I will go through:
- install Jenkins on Ubuntu
- clone and install a Python application in a virtualenv
- run tests using nose and publish tests results, code coverage and pylint reports
- have Github notify Jenkins when new code is pushed
With Ubuntu, Jenkins installs smoothly:
wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add - sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list' sudo aptitude update sudo aptitude install jenkins
Jenkins will start automatically on port 8080.
Setup a Jenkins job
Create a new job and call it something without spaces! Jenkins creates a directory of the same name on the filesystem, but pip (or virtualenv?) will choke on spaces. Then select "Build a free-style software project".
To clone a git repo, you can use the Jenkins GIT plugin. Once installed, go to "Configure System" and under "Git plugin" set the variables for Global "Config user.name" and "Global Config user.email" (required by the plugin to tag the source on each build).
Return to your project and under "Source Code Management", configure the repo you want to clone. I pass the git Read-Only URL.
If you run "Build Now", it should clone your Github repo. You can make sure the files show up under your project's "Workspace".
Run tests, build reports
Now we have the Python source code, we want to install it in a virtualenv. Under "Build", add a build step "Execute shell"
... and tweak the following code to fit your needs:
PYENV_HOME=$WORKSPACE/.pyenv/ # Delete previously built virtualenv if [ -d $PYENV_HOME ]; then rm -rf $PYENV_HOME fi # Create virtualenv and install necessary packages virtualenv --no-site-packages $PYENV_HOME . $PYENV_HOME/bin/activate pip install --quiet nosexcover pip install --quiet pylint pip install --quiet $WORKSPACE/ # where your setup.py lives nosetests --with-xcoverage --with-xunit --cover-package=myapp --cover-erase pylint -f parseable myapp/ | tee pylint.out
As much as I love virtualenvwrapper, don't use it -- it really doesn't make sense for automated tasks. Moveover, you want your job's workspace to be self-contained, whereas virtualenvwrapper wants to keep your virtualenvs in a common place, typically ~/.virtualenvs.
The environment variable
$WORKSPACE is made available during the script execution, it's the path to where Jenkins creates your build, e.g., /var/lib/jenkins/jobs/foo/workspace/.The package nosexcover (don't misread it) is a nose plugin that introduces the
--with-xcoverage nose option. It generates Cobertura-style XML reports.Interpreting reports
When nose runs, it will generate report files in your workspace that Jenkins has to interpret.
- nosetests.xml: This file is generated by the
--with-xunitoption and can be interpreted by checking "Publish JUnit test result report"
- coverage.xml: The coverage file is generated by
--with-xcoverage. Jenkins can interpret this report after installing the Cobertura plugin. It will be available as "Publish Cobertura Coverage Report", and the report pattern must be**/coverage.xml.
- pylint.out: For this one, you have to install the Violations plugin, which will make the checkbox "Report Violations" available. The plugin supports a bunch of different reports. Look for "pylint" and enter
**/pylint.outas the "XML filename pattern".
Run "Build Now" and tweak the Shell script until nose runs all the way through (regardless if your tests pass). Make sure the report files are generated. Eventually, you will get nice charts on your job's page.
Github, notify Jenkins!
Finally, we want to run the build every time your Github project receives new code (push). Let's have Jenkins ready to receive notifications from Github. We need to check "Trigger builds remotely (e.g., from scripts)".
![]() |
| NOTE: This checkbox only shows up if you check "Enable security" at the server configuration level and set a "Security Realm". |
Once enabled, set the "Authentication Token" to, e.g.,
GIT_PUSH_NOTIFY.Now go to your Github project page, click the "Admin" button to configure a "Post-Receive URLs" service hook, and copy/paste the URL given under the "Authentication Token" input field.
Finally replace, the JENKINS_URL part by the URL to your own Jenkins server. Github has a "Test hook" button to simulate a code push and trigger an actual HTTP call. Jenkins should start building your Python project automatically.













Where do you find this interface? http://2.bp.blogspot.com/-tz3uP0bANkE/To5KBeL2_lI/AAAAAAAAEus/SsDR-Ut5aUE/s1600/jenkins-report-nosetests.png
ReplyDeleteI think maybe I'm running an older version of Jenkins because there's no where in the job config, that I can see, to have it interpret XUnit format results.
Hi Dan,
ReplyDeleteHum... I'm not sure. I just installed Jenkins (v 1.424.6) from Ubuntu 12.04's package repository and this option shows up without any plugin.
I see that there's a "xUnit Plugin" available from the Jenkins plugin manager. I'm not sure how much this one differs from this tutorial but maybe you want to give it a try: https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin
Or upgrade your Jenkins if this is an option.
Good luck!
Very clear instructions, thankyou.
ReplyDeleteI have followed for my first python/jenkins setup: http://jenkins.paneris.net/job/PyCLU/
Also followed your instructions. Thank you very much.
ReplyDeleteWhats the advantage of cleaning .pyenv/ on every build? Or is it totally wrong not to do it?
It just feels cleaner to me. I delete .pyenv just to make sure that my project builds from scratch. Say that you had a library installed in your .pyenv because setup.py required it, but you no longer use, so you removed it from setup.py (which makes sense). But in the future, you add this library back into your project but forget to update setup.py. Well if your .pyenv had the left-over library from the past, all of your tests would still pass and you wouldn't know that you are missing the library requirement in setup.py.
DeleteHey!!!
ReplyDeleteJust f*cking amazing what you showed in this tutorial !!!
Now i'm able to really see how my project improves on every push.
Thank you!
Hello,
ReplyDeleteJuste a quick question: it is possible to use the nosexcover with the --cover-inclusive option ? Otherwise the coverage results can be misleading since it outputs 100% for package never covered.
Hum... I never noticed this. Do you have a sample output?
DeleteSorry actually I misphrased the issue. My issue is that Python files that are never imported are not part of the coverage result file. Meaning that if I have 2 python files, with one 100% covered and one never called during the tests, I will have a 100% coverage of my project in Jenkins.
DeleteYou can also add
ReplyDeleteexport PIP_DOWNLOAD_CACHE=$JENKINS_HOME/.pipcache
to cache pip downloads.
Awesome article. Thanks a lot.
ReplyDeleteI am using it to test a Django app, and only had to change the line:
nosetests --with-xcoverage --with-xunit --cover-package=myapp --cover-erase
. . . to:
python manage.py test --with-xcoverage --with-xunit --cover-package=myapp --cover-erase myapp
And be sure to add django-nose [1] to the settings.py.
[1] https://github.com/jbalogh/django-nose
This comment has been removed by the author.
ReplyDeleteI Jenkins installed on a remote server(not sure what OS it is)
ReplyDeleteand python test file which have many test cases( i.e def() )
When i run the Jenkins job .It gives output on console where certain test pass and other fails but at the end of the console output it shows:
**********************************************************************************************************************************
------------------------------------------------------------
Test complete. Result code = 0
STS: smoke_1
Unlock Code: 2014021009141392041655
------------------------------------------------------------
Archiving artifacts
Email was triggered for: Success
Sending email for trigger: Success
Sending email to: xxx
Notifying upstream projects of job completion
Finished: SUCCESS
***********************************************************************************************************************
Why is the Result Code = 0 above when some test pass and others fail in the python file,
how can i correct the result code to non zero value?
Is it some error with Jenkins or Python code?
The command that you ask Jenkins to run seems to return an exit status 0. If you run this same command manually from your own terminal, do you also get 0? (you can check by running "echo $?" right after running your test command, more info here: http://www.tldp.org/LDP/abs/html/exit-status.html#EXITSTATUSREF) If you do get 0, try to figure out why the command succeeded.
DeleteThanks for reply Alex,
DeleteI use Jenkins via browser on windows machine, so not have much idea of terminal commands. But let me tell you the code:
This is my code:
print 'result code before subprocess: '
print result_code
result_code = subprocess.call(command)
print 'result code after subporcess: '
print result_code
Before subprocess.call(command) result_code is -1(default)
after subprocess.call(command) result_code is 0
where command = 'jenkins/abc.sh'
where abc.sh calls abc.py file from inside it !
Will subprocess.call(command ) return zero if all def() inside abc.py are executed successfully
or will it return zero if all def() inside abc.py return passed ?
In my case some def() passed and some failed but still subprocess.call(command) returns zero !
Is is right ?
What if i want subprocess.call(command) return non zero if some def() failed inside abc.py ?
What do you mean by all def() are executed successfully, what qualifies them as having ran successfully or not? I could go back-and-forth with you to help you debug your scripts but I feel that you may not be using the best practices with regards to testing in Python. Usually you'd want to use a test runner, such as py.test or nosetests, that will take care of finding Python files and functions that start with the word "test_" and run them for you, record successes and failures and build a report with the proper exit code that you would expect. Make sure that your test code uses Python assertions (with the "assert" statement). Here is a getting started example: http://pytest.org/latest/getting-started.html
DeleteI hope this helps, good luck!
Yes, you are right! This is not the best practice as these are old test scripts. We are using nose for new test cases but old test cases are not with nose. I am new to testing with python and command line testing .(Done Web application testing before this . always!).
DeleteBy def() i mean all test methods which are called from main()'s init() method
I see. Well, make sure that when a test fails Python exits by raising an exception (which should exit with a non-zero value) or by explicitly making Python quit with sys.exit(1) (or by passing any non-zero value). For some reason your Python code calls a command that seems to execute successfully, thus returning 0. So you may want to make sure that whatever command is passed to subprocess.call() is indeed failing and returning / setting return_code to something else than 0. That being said even if the command had returned non-zero your Python script could mask this information and still exit cleanly (0). You must exit Python by raising an exception (crashing) or by propagating the return_code variable upon exit: if return_code != 0: sys.exit(return_code). Or something like: if return_code != 0: raise Exception("command failed: %s" % command)
Deleteyeah , this is what i was looking for
DeleteBut if i do sys.exit(1) . It exits and do not execute remaining test.
If i do raise Exception('fail') It still exits and do not execute any remaining test
Can i exit a code block, exception saved and run code further and finally return the return_code= 1 as 1st test return exception and 2nd test passed?
Sure, you can do whatever you want and that's really up to you how you want to handle this. If you want to keep track of which test succeeded and which failed, in other words build a report, I'll let you be creative here. But nose does that for you already, so maybe all you'd have to do is to rename all your test functions with the leading "test_" prefix, assert/raise accordingly and update your abc.sh script to invoke "nosetests test_abc.py" and let it build the report for you. I'm just suggesting that it might not be that difficult to migrate your old tests to be nose compatible just by renaming your function names (and renaming the file name with "test_" as illustrated above). I hope you figure it out! Thanks for reading my blog! :)
DeleteThanks, a lot for your help!
DeleteYour blog rocks :)
Hi I have a local project and I used
ReplyDeletePYENV_HOME=$WORKSPACE/.pyenv/
# Delete previously built virtualenv
if [ -d $PYENV_HOME ]; then
rm -rf $PYENV_HOME
fi
# Create virtualenv and install necessary packages
virtualenv --no-site-packages $PYENV_HOME
pip install -U pytest
. $PYENV_HOME/bin/activate
py.test --junitxml ./Tests/results.xml
it gives me error
/tmp/hudson3711722204035418008.sh: 13: /tmp/hudson3711722204035418008.sh: py.test: not found
And when I tried adding WORKSPACE in Advanced Project Options it gives me a error
java.io.IOException: Failed to mkdirs:
Is there a folder structure for a local python workspace to run Jenkins properly.
I really don't understand how to add a project properly to Jenkins workspace.
Would you explain the process.
Thank you in advance.
Hi Rukshan,
DeleteThe problem I see here is that you tried to installed pytest outside of your virtualenv (before activating your virtualenv) so pytest got installed elsewhere but not in your virtualenv (I would expect the install to fail if you didn't call it with "sudo", which is not a good idea anyway; always install python packages in a virtualenv unless you want to mess with your system's Python packages or you know what you are doing).
So make sure you activate your virtualenv first and then install pytest before running it. Alternatively, you could skip the activation process completely if you ensure that you always explicitly specify the path to the executables in the bin/ directory if your virtualenv. For example, rather than just running "py.test --junitxml ./Tests/results.xml" you could do ".pyenv/bin/py.test --junitxml ./Tests/results.xml" -- or if you want to use pip without activation, you could do ".pyenv/bin/pip install pytest".
For the second part about the Jenkins WORKSPACE, I'm unsure. First see if my suggestion above shows progress.
I hope this helps.
Alex
This comment has been removed by the author.
ReplyDeleteit probably means that your project is not built as a standard Python package. Do you have a setup.py for instance ?
DeleteSee
https://docs.python.org/2/distutils/setupscript.html
Thanks a lot and sorry for deleting my comment i didn't saw your message but i solved it by adding setup.py thanks again :)
Delete