Skip to main content

Using travis-ci with miniconda, scipy, and nose

2015-06-16programmingpythonsoftware

Twice in the past year I have struggled to get continuous builds and test coverage set up on Travis and Coveralls (respectively). The first time was so frustrating that I just gave up, but last week I finally got things working! I thought it would be helpful to describe what worked (and why).

Probably the information I put here will slowly become outdated, but for the most recent copy of a working .travis.yml file, please see the setup at the theanets repository.

Here are the contents of the Travis config file; I’ll break things down (at least as far as I understand them) bit by bit.

sudo: false
language: python
python:
  - "2.7"
  - "3.4"
cache: apt
addons:
  apt:
    packages:
    - libatlas-dev
    - libatlas-base-dev
    - liblapack-dev
    - gfortran
before_install:
  - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh
  - bash miniconda.sh -b -p $HOME/miniconda
  - export PATH="$HOME/miniconda/bin:$PATH"
  - conda update --yes conda
install:
  - conda install --yes python=$TRAVIS_PYTHON_VERSION pip numpy scipy nose
  - pip install pep8 python-coveralls
  - pip install .
script:
  - THEANO_FLAGS=floatX=float32 nosetests --with-coverage --cover-package=theanets --logging-level=INFO
after_success:
  - coveralls

The sudo: false line enables builds on the new Docker infrastructure for Travis. The language: python line and the python: section enable a Python test.

The next two sections (the cache: apt line and the addons: apt: section) enable us to install some additional Ubuntu packages to our test environment. In this case, the config file installs packages that are needed to support the numpy and scipy libraries.

The before_install section is where the magic starts. It first downloads the latest version of the miniconda installation script:

  - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh

Next it runs the script and adds the installed environment to the PATH:

  - bash miniconda.sh -b -p $HOME/miniconda
  - export PATH="$HOME/miniconda/bin:$PATH"

Finally, the config updates the Conda environment to the latest release. This appears to be necessary because miniconda releases sometimes lag behind conda releases:

  - conda update --yes conda

So far, everything has come from the Travis CI Python docs and the Conda Travis docs. The Travis docs are not super helpful by themselves if your project requires scipy—installing scipy manually using pip makes your builds take forever, not to mention being a colossal waste of a fantastic, free computational resource. I found the Conda docs to be super confusing since the focus is more on using Conda to distribute packages, while I just wanted to use Conda for Travis builds.

Anyway, the build environment is set up and ready to install things! Here is where things got pretty tricky.

I use the install section of the build config to install packages that theanets needs for testing. This is confusing because now that conda is set up on the Travis build machine, there are actually two environments where Python packages can be installed: the Travis environment, and the Conda one. The Conda environment is sort of a neglected stepchild in the Travis environment; if you don’t explicitly enable it, the Travis environment will be used.

With that in mind, the config file ventures into risky territory by installing some packages built specially for Conda:

  - conda install --yes python=$TRAVIS_PYTHON_VERSION pip numpy scipy nose

There are two very important packages on this line: pip and nose. These are both important for related reasons that seem to revolve around paths in the build environment.

  1. It’s really important to conda install pip. If you don’t, then if you later try to pip install foo, the version of pip that serves your request will install things into the Travis environment, not the Conda one! Installing pip using conda ensures that the pip that serves your request will know to put things in the Conda environment.
  2. If you use nose to run your tests, then you also need to conda install nose! The reason here seems to be quite similar to the first point: if you don’t install nose using conda, then the version of nose that runs your tests will look in the Travis environment, not the Conda one! (This took me a few hours of banging-head-on-desk to figure out.)

The other two packages are just dependencies of my project. No biggie.

Next, the config installs a couple of additional dependencies using pip:

  - pip install pep8 python-coveralls

Why pip? As far as I understand, it’s because Conda doesn’t have access to all of the packages on the Python package index. I’m not sure why this is the case, but if you try to conda install these, it won’t work. Again, this only works here because we did conda install pip above.

Finally, the config sets up the actual theanets package for testing:

  - pip install .

With that, the tests are ready to run. I use nose to run tests for theanets; until I installed nose using conda install, this step was giving me all sorts of grief.

script:
  - nosetests --with-coverage --cover-package=theanets --logging-level=INFO

This line is prefaced by THEANO_FLAGS=floatX=float32 to set up an important Theano configuration variable—if you don’t set this explicitly, then your tests involving Theano might fail in different environments based on the types of floating-point data that you use.

Finally, the coveralls tool uploads code coverage results from your tests, and then you get lovely clickable source maps showing where your test coverage needs work.

after_success:
  - coveralls

After I got things working, I spent a couple hours increasing test coverage for the project. It’s quite the motivator to see that big red number come up for your project!

Happy coding (and testing)!