Robot Framework源码阅读02——参数解析

RobotFramework类的初始化

上一篇讲到了run_cli函数把命令行中的参数以列表的形式传给了RobotFramework类中的execute_cli函数。

先看下RobotFramework类的__init__初始化方法:

class RobotFramework(Application):

    def __init__(self):
        Application.__init__(self, USAGE, arg_limits=(1,),
                             env_options='ROBOT_OPTIONS', logger=LOGGER)

调用的是Application类的__init__初始化方法:

class Application(object):

    def __init__(self, usage, name=None, version=None, arg_limits=None,
                 env_options=None, logger=None, **auto_options):
        self._ap = ArgumentParser(usage, name, version, arg_limits,
                                  self.validate, env_options, **auto_options)
        self._logger = logger or DefaultLogger()

self._ap是一个ArgumentParser实例,可以推测是这里对传进来的参数做具体处理。

ArgumentParser

ArgumentParser字面意思就是参数解析器,来看看具体是怎么初始化的:

class ArgumentParser(object):

    def __init__(self, usage, name=None, version=None, arg_limits=None,
                 validator=None, env_options=None, auto_help=True,
                 auto_version=True, auto_escape=True, auto_pythonpath=True,
                 auto_argumentfile=True):
        """Available options and tool name are read from the usage.

        Tool name is got from the first row of the usage. It is either the
        whole row or anything before first ' -- '.
        """
        if not usage:
            raise FrameworkError('Usage cannot be empty')
        self.name = name or usage.splitlines()[0].split(' -- ')[0].strip()
        self.version = version or get_full_version()
        self._usage = usage
        self._arg_limit_validator = ArgLimitValidator(arg_limits)
        self._validator = validator
        self._auto_help = auto_help
        self._auto_version = auto_version
        self._auto_escape = auto_escape
        self._auto_pythonpath = auto_pythonpath
        self._auto_argumentfile = auto_argumentfile
        self._env_options = env_options
        self._short_opts = ''
        self._long_opts = []
        self._multi_opts = []
        self._flag_opts = []
        self._short_to_long = {}
        self._expected_args = ()
        self._create_options(usage)
        
   ...

非常长的一大段,都是为这个类里的各种属性设置初始值,只需要看最后一行:

self._create_options(usage)

_create_options这个函数是用来创建选项,选项是指我们前面说到的-L, -d, -t这些。

选项的创建(create options)

选项的创建,就是把RF支持的选项构造出来,来看看是怎么具体实现的,也是在ArgumentParser类里:

class ArgumentParser(object):
    ...
    
    def _create_options(self, usage):
        for line in usage.splitlines():
            res = self._opt_line_re.match(line)
            if res:
                self._create_option(short_opts=[o[1] for o in res.group(1).split()],
                                    long_opt=res.group(3).lower(),
                                    takes_arg=bool(res.group(4)),
                                    is_multi=bool(res.group(5)))

_create_options只接收里一个参数usage,拿变量usage里的每一行去做正则匹配:

  • 用 splitlines() 按照行分隔符比如\r, \n, \r\n分隔,得到一个包含每一行作为元素的列表
  • 用 re 模块里的 match() 函数,匹配含有选项用法的行
  • 用 re 模块里的 group() 提取出该行匹配到的每一个分组内容,分别进行处理

匹配上的例如-L --loglevel level这样的行,再用_create_option函数根据匹配的分组内容去创建具体的选项,比如

  • short_opts=['L'] (可以有多个短选项对应一个长选项)
  • long_opt='loglevel'
  • takes_arg=True
  • is_multi=False

USAGE

usage具体的值是在RobotFramework类里传入的常量USAGE,它定义在run.py里,非常长的一大段话,截取点主要内容:

USAGE = """Robot Framework -- A generic test automation framework

Version:  

Usage:  robot [options] data_sources
   or:  python -m robot [options] data_sources
   or:  python path/to/robot [options] data_sources
   or:  java -jar robotframework.jar [options] data_sources

Robot Framework is a Python-based keyword-driven test automation framework for

...

Options
=======

 -F --extension value     Parse only files with this extension when executing
                          a directory. Has no effect when running individual
                          files or when using resource files. If more than one
                          extension is needed, separate them with a colon.
                          Examples: `--extension robot`, `-F robot:txt`
                          New in RF 3.0.1.
 -N --name name           Set the name of the top level test suite. Underscores
                          in the name are converted to spaces. Default name is
                          created from the name of the executed data source.

...

re

re库是Python里非常常用的一个库,一般用法有:

  • re.compile,编译正则表达式
  • match,传入字符串进行匹配
  • group/groups,提取匹配到每个分组里的字符串

RF这里用来匹配USAGE中选项用法的正则表达式如下:

class ArgumentParser(object):
    _opt_line_re = re.compile('''
    ^\s{1,4}      # 1-4 spaces in the beginning of the line
    ((-\S\s)*)    # all possible short options incl. spaces (group 1)
    --(\S{2,})    # required long option (group 3)
    (\s\S+)?      # optional value (group 4)
    (\s\*)?       # optional '*' telling option allowed multiple times (group 5)
    ''', re.VERBOSE)

re.VERBOSE可以把正则表达式写成多行,并且自动忽略空格。

这里一共五组规则,第一个规则是以一到四个空格开头,匹配到的返回匹配结果,匹配不到的返回None:

import re
space4_re = re.compile("^\s{1,4}")
space4_re.match("abc")  # None
space4_re.match("   abc")  # <_sre.SRE_Match object; span=(0, 3), match='   '>
space4_re.match("    abc")  # <_sre.SRE_Match object; span=(0, 4), match='    '>

第二个规则是可能存在的短选项,如-t --test name *中的-t

第三个规则是长选项,如-t --test name *中的--test

第四个规则是可能存在的选项值

第五个规则是星号*用法,带星号的选项允许出现若干次

下面例子来具体看下,每个group里都是什么

_opt_line_re.match(" -t --test name *").groups()

# ('-t ', '-t ', 'test', ' name', ' *')

_opt_line_re.match(" -h -? --help").groups()
# ('-h -? ', '-? ', 'help', None, None)

创建单个选项(create option)

class ArgumentParser(object):
    ...
    
    def _create_option(self, short_opts, long_opt, takes_arg, is_multi):
        self._verify_long_not_already_used(long_opt, not takes_arg)
        for sopt in short_opts:
            if sopt in self._short_to_long:
                self._raise_option_multiple_times_in_usage('-' + sopt)
            self._short_to_long[sopt] = long_opt
        if is_multi:
            self._multi_opts.append(long_opt)
        if takes_arg:
            long_opt += '='
            short_opts = [sopt+':' for sopt in short_opts]
        else:
            if long_opt.startswith('no'):
                long_opt = long_opt[2:]
            self._long_opts.append('no' + long_opt)
            self._flag_opts.append(long_opt)
        self._long_opts.append(long_opt)
        self._short_opts += (''.join(short_opts))

这里主要做了几件事:

  • 保存短选项和长选项之间映射关系,存在字典self._short_to_long里,如{'F': 'extension', 'N': 'name', 'D': 'doc', 'M': 'metadata', 'G': 'settag', 't': 'test', 's': 'suite', 'i': 'include', 'e': 'exclude'}
  • 如果是允许多次使用的选项,加到self._multi_opts,如['metadata', 'settag', 'test', 'suite', 'include', 'exclude']
  • 如果是需要参数的选项,长选项后面加上'=',短选项后面加上':';如果是不需要参数的选项,加入到self._flag_opts里,加no前缀再加入到self._long_opts
  • self._long_opts里保存长选项列表,如['extension=', 'name=', 'doc=', 'metadata=', 'settag=', 'test=', 'suite=', 'include=', 'exclude=']
  • self._short_opts里保存短选项字符串,如'F:N:D:M:G:t:s:i:e:R:S:c:n:v:V:d:o:l:r:x:b:TL:X.W:C:K:P:E:A:h?'

至此,得到了RF支持的所有选项和用法。

解析命令行参数

现在回到RobotFramework类中的execute_cli函数上
,看下RobotFramework类的定义:

class RobotFramework(Application):

    def __init__(self):

    def main(self, datasources, **options):

    def validate(self, options, arguments):

    def _filter_options_without_value(self, options):

这里并没有重写execute_cli方法,所以RobotFramework类是继承了Application类中的execute_cli方法。

class Application(object):
    ...
    
    def execute_cli(self, cli_arguments, exit=True):
        with self._logger:
            self._logger.info('%s %s' % (self._ap.name, self._ap.version))
            options, arguments = self._parse_arguments(cli_arguments)
            rc = self._execute(arguments, options)
        if exit:
            self._exit(rc)
        return rc

顺藤摸瓜,依次调用:

  • 调用了Application类的_parse_arguments方法
  • 再调用了parse_arguments方法(这是一个解析命令行参数的公共接口,接收一个列表作为参数)
  • 再调用了ArgumentParser类实例self._ap.parse_args方法
  • 再调用了_parse_args方法

所以直接来看下_parse_args方法:

class ArgumentParser(object):
    ...

    def _parse_args(self, args):
        args = [self._lowercase_long_option(a) for a in args]
        try:
            opts, args = getopt.getopt(args, self._short_opts, self._long_opts)
        except getopt.GetoptError as err:
            raise DataError(err.msg)
        return self._process_opts(opts), self._glob_args(args)

用的是getopt库分别获取到opts和args

getopt

学习一下getopt的使用,一个简单例子,在rf-reader/02/myops.py文件:

import sys
import getopt

opts, args = getopt.getopt(sys.argv[1:], "L:d:t:", ["loglevel=", "test=", "outputdir="])

print(opts)
print(args)

在命令行执行:

python -m 02.myops  -L DEBUG -d output --test testcasename testfilename

打印结果:

[('-L', 'DEBUG'), ('-d', 'output'), ('--test', 'testcasename')]
['testfilename']
  • opts为已定义的选项及其值(定义的短选项和长选项分别通过getopt中的第二个和第三个参数传入)
  • args为剩下的不属于已定义的选项格式信息的命令行参数,即短选项和长选项以外的参数。

现在再回头看下_parse_args方法结尾出的两个方法:

  • _process_opts,将[('-L', 'DEBUG'), ('-d', 'output'), ('--test', 'testcasename')]这些元组处理成长选项的字典{'loglevel':'DEBUG', 'outputdir':'output', 'test':'testcasename'}
  • _glob_args,返回的是文件的路径

最后,回顾下execute_cli方法,它会将解析出来的参数opts和args会传入_execute中去执行:

def execute_cli(self, cli_arguments, exit=True):
    with self._logger:
        self._logger.info('%s %s' % (self._ap.name, self._ap.version))
        options, arguments = self._parse_arguments(cli_arguments)
        rc = self._execute(arguments, options)
    if exit:
        self._exit(rc)
    return rc

参数解析到此结束,后面就是真正的执行部分了。

你可能感兴趣的:(Robot Framework源码阅读02——参数解析)