Openstack工程的持续集成实践--tox
问题导读
1.tox的作用是什么?
2.使用keystone自带的tox.ini执行单元测试和静态检查时,遇到了哪些问题?
static/image/hrline/4.gif
以keystone工程为例,其他工程类似
内容包括:打源码包(sdist)、单元测试(UT)、测试覆盖率(coverage)、代码格式检查(pep8,flake)
1、引子接触了一段时间openstack社区,并提交了几个bug之后,就发现社区中,从bug提交、问题确认、到bug修复,代码review,自动构建、单元测试、静态检查、再到代码合入,也就是我们经常说的持续集成(CI),是一个非常简单和高效的过程。
开发人员都是懒人,这点我从来都没有怀疑过,怎么让一群懒人(还是一大群)将CI的这么多步骤做规范,并且不觉得是一个麻烦的过程,需要很高的技巧和聪明才智。
我认为持续集成(CI)有两个重点需要把握,首先要好上手,简单易学,开发都是懒人,不好用的东西,很难养成习惯使用;其次做且仅做应该做的事,也就是CI检查的范围要确定,保持CI的高速,写完代码10分钟之内,要出ut和coverage的结果。
来让我们看看openstack社区的持续集成都包括哪些内容,使用了哪些工具。
2、tox对openstack几个核心工程代码比较熟悉的朋友,可能都会注意到代码根目录下都有个tox.ini文件,tox其实就是openstack持续集成中非常重要的一个工具,tox.ini就是tox的配置文件。
tox的官方对于tox的定义是这样的:
Tox as is a generic virtualenv management and test command line tool
http://tox.readthedocs.org/en/latest/
也就是一个通用的虚拟环境管理和测试命令行工具。
所谓的虚拟环境,就是可以在一个主机上,自定义出多套的python环境,多套环境中使用不同的python拦截器,环境变量设置,第三方依赖包,执行不同的测试命令,最重要的是各个环境之间互不影响,相互隔离。
最典型的应用就测试在不同python版本下代码的兼容性,我们可以为py2.4,py2.5,py2.6,py2.7创建不同的虚拟环境,都可以用tox统一管理;也可以在tox.ini中自定义虚拟环境,例如:testevn:pep8,代码格式检查;testenv:cover,测试覆盖率。
我们以最新的H版的keystone的tox.ini为例:
首先定义tox的全局配置,列出了需要执行的虚拟环境列表,在命令行中直接执行tox,就会依次执行py26,py27,pep8
envlist = py26,py27,pep8
然后定义了虚拟环境的配置
[*]setenv列出了虚拟机环境中生效的环境变量,一些配色方案和单元测试标志;
[*]deps列出了虚拟环境需要的第三方依赖包,也就是keystone根目录下的requirements.txt和test-requirements.txt其中包括了keystone运行和单元测试时,需要用到的依赖包,每个虚拟环境创建的时候,会通过pip install -r requirements.txt和pip install -r test-requirements.txt安装依赖包到虚拟环境;
[*]commands就是在当前虚拟环境中需要执行的命令,python tools/patch_tox_venv.py就是安装了redhat-eventlet.patch补丁;nosetests {posargs}就是执行nose进行单元测试,{posargs}参数就是可以将tox的参数传递给nosetests,例如:tox -- --with-coverage执行的时候就是nosetests --with-coverage
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1
NOSE_OPENSTACK_COLOR=1
NOSE_OPENSTACK_RED=0.05
NOSE_OPENSTACK_YELLOW=0.025
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python tools/patch_tox_venv.py
nosetests {posargs}
自定义了一个pep8的代码静态检查的虚拟环境,执行flake8 --filename=keystone* bin
commands =
flake8
flake8 --filename=keystone* bin
定义了和CI server jenkins的集成配置,指定了pip的下载cache目录,提高构建虚拟环境的速度
downloadcache = ~/cache/pip
定义一个cover的虚拟环境,就是指定了一些环境变量,使单元测试的时候,自动应用coverage,并定义了coverage生成的html报告目录
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_COVERAGE=1
NOSE_COVER_HTML=1
NOSE_COVER_HTML_DIR={toxinidir}/cover
这个不太明白,也许就是创建一个虚拟机环境,执行一个自定义的命令行,以备扩展
commands = {posargs}
定义了flake8静态检查的一些细节配置
show-source = true
# H304: no relative imports.
ignore = H304
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools,vendor,.update-venv3、使用过程中的一些改进
直接使用keystone自带的tox.ini执行单元测试和静态检查时,也遇到了一些问题:
[*]每次执行tox命令的时候,所有的虚拟环境都会重建,重新用pip下载依赖包,时间都浪费在了下包上,recreate=False也不能解决,后来想了个招儿,先手动用pip将requirements.txt和test-requirements.txt都安装在系统python库下,然后将sitepackages=True,继承系统的依赖包。这样似乎打破了虚拟环境相互隔离的好处,但是能节省非常多的时间,大概70%。大家自己权衡是否需要使用这种方法。
[*]执行单元测试的时候,顺便生成单元测试报告,并检查测试覆盖率,并生成覆盖率报告。直接执行tox是不行的,只能进行单元测试,需要给tox增加扩展参数,如下:tox -- --cover-erase -- --with-coverage -- --cover-html
[*]一开始执行tox的时候,生成的coverage覆盖率报告都是0%,百思不得其解,后来发现keystone根目录下有个.coveragerc文件,这个文件是coverage的配置文件,会影响coverage的行为,将文件中的source = keystone注释掉之后,正常生成覆盖率报告。
学习了。我git keystone源码后,运行$tox -e py27,安装deps时,失败。手动pip安装没有问题。什么原因?如何解决?谢谢!
ERROR: py27: could not install deps [-r/home/dzhang/openstack-dev/keystone/test-requirements.txt, .]; v = InvocationError('/home/dzhang/openstack-dev/keystone/.tox/py27/bin/pip install -U -r/home/dzhang/openstack-dev/keystone/test-requirements.txt . (see /home/dzhang/openstack-dev/keystone/.tox/py27/log/py27-1.log)', 1)
谢谢分享!!!!! 我觉得我遇到的最大的问题是,经常报找不到包,我也安装了test-requirements.tx 和 requirement.txt 的包,例如
(novadev27) root@dev-u:/home/sunjiyun/17.0.7/nova# tox -e cover
/home/sunjiyun/17.0.7/nova/.tox/cover/local/lib/python2.7/site-packages/setuptools/dist.py:45: DistDeprecationWarning: Do not call this function
warnings.warn("Do not call this function", DistDeprecationWarning)
cover develop-inst-noop: /home/sunjiyun/17.0.7/nova
cover installed: DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.,actdiag==0.5.4,alabaster==0.7.10,appdirs==1.4.3,attrs==17.4.0,Babel==2.5.3,bandit==1.6.2,blockdiag==1.5.3,certifi==2018.1.18,chardet==3.0.4,cliff==2.11.1,cmd2==0.7.9,colorama==0.3.9,coverage==4.4.2,ddt==1.1.1,debtcollector==1.19.0,decorator==4.2.1,deprecation==1.0.1,docutils==0.14,dogpile.cache==0.6.4,dulwich==0.18.6,enum-compat==0.0.2,enum34==1.1.6,eventlet==0.20.0,extras==1.0.0,fasteners==0.14.1,fixtures==3.0.0,flake8==2.5.5,funcparserlib==0.3.6,funcsigs==1.0.2,functools32==3.2.3.post2,future==0.16.0,futures==3.2.0,gabbi==1.42.1,gitdb2==2.0.3,GitPython==2.1.8,greenlet==0.4.15,hacking==0.12.0,idna==2.6,imagesize==0.7.1,ipaddress==1.0.19,iso8601==0.1.12,Jinja2==2.10,jmespath==0.9.3,jsonpatch==1.21,jsonpath-rw==1.4.0,jsonpath-rw-ext==1.1.3,jsonpointer==2.0,jsonschema==2.6.0,keystoneauth1==3.4.0,linecache2==1.0.0,lxml==4.1.1,MarkupSafe==1.0,mccabe==0.2.1,mock==2.0.0,monotonic==1.4,mox3==0.24.0,msgpack==0.5.1,munch==2.2.0,netaddr==0.7.19,netifaces==0.10.6,nova==3.1.1,openstackdocstheme==1.22.0,openstacksdk==0.11.3,os-api-ref==1.5.0,os-client-config==1.29.0,os-service-types==1.1.0,osc-lib==1.9.0,oslo.concurrency==3.25.1,oslo.config==5.2.1,oslo.i18n==3.19.0,oslo.serialization==2.24.0,oslo.utils==3.35.1,oslo.vmware==2.26.0,oslotest==3.2.0,osprofiler==1.15.2,pbr==3.1.1,pep8==1.5.7,Pillow==5.0.0,pluggy==0.6.0,ply==3.10,prettytable==0.7.2,psycopg2==2.7.3.2,py==1.5.2,pyflakes==0.8.1,Pygments==2.2.0,PyMySQL==0.8.0,pyparsing==2.2.0,pyperclip==1.6.0,pytest==3.3.2,python-barbicanclient==4.6.1,python-ironicclient==2.2.2,python-mimeparse==1.6.0,python-pcre==0.7,python-subunit==1.2.0,pytz==2017.3,PyYAML==3.12,reno==2.7.0,requests==2.18.4,requests-mock==1.4.0,requestsexceptions==1.4.0,rfc3986==1.1.0,seqdiag==0.9.5,simplejson==3.13.2,six==1.11.0,smmap2==2.0.3,snowballstemmer==1.2.1,Sphinx==1.6.5,sphinxcontrib-actdiag==0.8.5,sphinxcontrib-seqdiag==0.8.5,sphinxcontrib-websupport==1.0.1,stestr==2.0.0,stevedore==1.28.0,suds-jurko==0.6,testrepository==0.0.20,testresources==2.0.1,testscenarios==0.5.0,testtools==2.3.0,traceback2==1.4.0,typing==3.6.2,unicodecsv==0.14.1,unittest2==1.1.0,urllib3==1.22,voluptuous==0.10.5,webcolors==1.7,WebOb==1.7.4,whereto==0.4.0,wrapt==1.10.11,wsgi-intercept==1.6.0
cover run-test-pre: PYTHONHASHSEED='3527988970'
cover run-test: commands | coverage erase
cover run-test: commands | find . -type f -name '*.pyc' -delete
cover run-test: commands | stestr run ''
=========================
Failures during discovery
=========================
--- import errors ---
Failed to import test module: nova.tests.unit
Traceback (most recent call last):
File "/home/sunjiyun/17.0.7/nova/.tox/cover/local/lib/python2.7/site-packages/unittest2/loader.py", line 490, in _find_test_path
package = self._get_module_from_name(name)
File "/home/sunjiyun/17.0.7/nova/.tox/cover/local/lib/python2.7/site-packages/unittest2/loader.py", line 395, in _get_module_from_name
__import__(name)
File "nova/__init__.py", line 33, in <module>
import oslo_service# noqa
ImportError: No module named oslo_service
================================================================================
The above traceback was encountered during test discovery which imports all the found test modules in the specified test_path.
ERROR: InvocationError for command /home/sunjiyun/17.0.7/nova/.tox/cover/bin/stestr run '' (exited with code 100)
_________________________________________________________________________________________________ summary __________________________________________________________________________________________________
ERROR: cover: commands failed
页:
[1]