Hey,

oftentimes I see myself having to run two types of tests in travis-ci: unitary and integration tests.

For the first category, that usually means running some plain Golang code that interacts with no other services (e.g, it uses a mocked database or it mocks HTTP requests). In the other scenario, spawning Docker containers which end up creating a database that is used in the test - requiring docker to be present, thus, VM-based builds.

Illustration of concurrently running VM and container-based builds

Using the matrix property we can tailor our build matrix and make use of both types of build infrastructures: container-based (pretty fast, super good for unit tests) and VM-based (pretty good for raising services and Docker containers).

Snippet

Below is a very commented snippet (.travis.yml) detailing how to run more than one type of build infrastructure. Note that here I’m making use of language: go but that works just fine for anything else.

language: 'go'

go:
  - '1.9.1'

# `matrix` allows us to include or exclude environments
# specifying properties for each of them.
#
# As `sudo` is a property like any other, we can change
# it in `matrix` such that we can have one build environment
# running in a container - useful for very quick unit tests - and 
# another build running in a Travis VM - where we can make
# use of `docker` and create many containers, very suitable
# for integration testing.
matrix:
  include:
    # mark via environment-variables that we're in a unit-test
    - env: 'TEST_SUITE=unit'
      sudo: false

    # sudo-enabled specific configuration
    - env: 'TEST_SUITE=integration'
      sudo: true
      services:
        - 'docker'
      # most of the times I set a `setup.sh` script that updates
      # the image dependencies so that if I want to debug the
      # build later it's very easy to run all the setup by just
      # running a single command.
      before_install: 
        - './.travis/setup.sh'
      before_script:
        - 'make image'

# These properties that are not in `include` will be run for both
# environments.
install:
  - 'make'

# Having `test-<TEST_SUITE>` different in each environment we can
# then run different types of tests in different environments.
script:
  - make test-${TEST_SUITE}

# Evict sending notifications when things fail.
# I'm definitely not a fan of being alerted by
# this when developing.
notifications:
  email: false

That’s it! Let’s create a test project and check how that works.

  1. create a new GitHub repository (in this case I’m creating cirocosta/travis-multiinfra);
  2. sync your account in travis-ci.org or travis-ci.com (works for both offerings);
  3. enable builds for that repository
  4. populate the repo with some code and a yaml file like the above
  5. push to git and check the build at Travis-ci.

In cirocosta/travis-multiinfra’s .travis.yml I have a simplified .travis.yml:

language: 'go'

go:
  - '1.9.1'

matrix:
  include:
    - env: 'TEST_SUITE=unit'
      sudo: false

    - env: 'TEST_SUITE=integration'
      sudo: true
      services:
        - 'docker'

install:
  - 'true'

script:
  - 'true'

notifications:
  email: false

When your build starts you should see two instantiations: one for the container-based build and another for the VM-based.

Note: the amount of parallelization depends on your plan. If you’re on the free plan on travis.org or you’re already running other builds you won’t be able to execute multiple builds in parallel. You’ll still have the multiple infrastructures though.

Image of the Travis jobs console

To check configuration was used for each job, head to one of them and then click on view config.

Here’s how the unitary (container) looks like:

{
    "dist": "trusty",
    "env": "TEST_SUITE=unit",
    "go": "1.9.1",
    "group": "stable",
    "install": [
        "true"
    ],
    "language": "go",
    "os": "linux",
    "script": [
        "true"
    ],
    "sudo": false
}

and the integration (VM):

{
    "dist": "trusty",
    "env": "TEST_SUITE=integration",
    "go": "1.9.1",
    "group": "stable",
    "install": [
        "true"
    ],
    "language": "go",
    "os": "linux",
    "script": [
        "true"
    ],
    "services": [
        "docker"
    ],
    "sudo": true
}

Note that the vm-based contains the service as we want but the first (container-based) doesn’t.

Closing thoughts

Being a long time Travis-ci user I really enjoy the flexibility that it brings as well as the peace of mind that having a CI infrastructure setup brings. I got very excited when Travis announced container-based build and migrated some projects right away (it was mostly a matter of putting sudo: false and replacing the apt get ... calls). Their container-based builds are pretty fast and can do a bunch for sure but not everything is doable there. For these other situations, the VM-based runtime is needed and being able to run both of them without changing a lot the configuration (or worse, imagine if I had to create a separate repository?) is pretty good.

If you want to know more, make sure you look at their official docs on build customization: docs.travis-ci: customizing the build.

I’ll be following up soon on how I publish this blog. As a heads up, I use Travis to build the static files and sync with an S3 bucket that serves the content to CloudFront. If you want to know more, make sure you subscribe to the mailing list 👌

Please let me know if I got something wrong.

Thanks!

finis