Travis CI

In this section we are going to use Travis CI as our continuous-integration service. Travis CI is mostly used for building and running tests for projects hosted at GitHub. It supports different programming laguages and for our particular case, it supports the Crystal language.

Note:If you are new to continuous integration (or you want to refresh the basic concepts) we may start reading the core concepts guide.

Now let’s see some examples!

Build and run specs

Using latest and nightly

A first (and very basic) Travis CI config file could be:

  1. # .travis.yml
  2. language: crystal

That’s it! With this config file, Travis CI by default will run crystal spec.
Now, we just need to go to Travis CI dashboard to add the GitHub repository.

Let’s see another example:

  1. # .travis.yml
  2. language: crystal
  3. crystal:
  4. - latest
  5. - nightly
  6. script:
  7. - crystal spec
  8. - crystal tool format --check

With this configuration, Travis CI will run the tests using both Crystal latest and nightly releases on every push to a branch on your Github repository.

Note: When creating a Crystal project using crystal init, Crystal creates a .travis.yml file for us.

Using a specific Crystal release

Let’s suppose we want to pin a specific Crystal release (maybe we want to make sure the shard compiles and works with that version) for example Crystal 0.31.1.

Travis CI only provides runners to latest and nightly releases directly and so, we need to install the requested Crystal release manually. For this we are going to use Docker.

First we need to add Docker as a service in .travis.yml, and then we can use docker commands in our build steps, like this:

  1. # .travis.yml
  2. language: minimal
  3. services:
  4. - docker
  5. script:
  6. - docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 crystal spec

Note: We may read about different (languages)[https://docs.travis-ci.com/user/languages/] supported by Travis CI, included minimal.

Note: A list with the different official Crystal docker images is available at DockerHub.

Using latest, nightly and a specific Crystal release all together!

Supported runners can be combined with Docker-based runners using a Build Matrix. This will allow us to run tests against latest and nightly and pinned releases.

Here is the example:

  1. # .travis.yml
  2. matrix:
  3. include:
  4. - language: crystal
  5. crystal:
  6. - latest
  7. script:
  8. - crystal spec
  9. - language: crystal
  10. crystal:
  11. - nightly
  12. script:
  13. - crystal spec
  14. - language: bash
  15. services:
  16. - docker
  17. script:
  18. - docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 crystal spec

Installing shards packages

In native runners (language: crystal), Travis CI already automatically installs shards dependencies using shards install. To improve build performance we may add caching on top of that.

Using Docker

In a Docker-based runner we need to run shards install explicitly, like this:

  1. # .travis.yml
  2. language: bash
  3. services:
  4. - docker
  5. script:
  6. - docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 shards install
  7. - docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 crystal spec

Note: Since the shards will be installed in ./lib/ folder, it will be preserved for the second docker run command.

Installing binary dependencies

Our application or maybe some shards may required libraries and packages. This binary dependencies may be installed using different methods. Here we are going to show an example using the Apt command (since the Docker image we are using is based on Ubuntu)

Here is a first example installing the libsqlite3 development package using the APT addon:

  1. # .travis.yml
  2. language: crystal
  3. crystal:
  4. - latest
  5. before_install:
  6. - sudo apt-get -y install libsqlite3-dev
  7. addons:
  8. apt:
  9. update: true
  10. script:
  11. - crystal spec

Using Docker

We are going to build a new docker image based on crystallang/crystal, and in this new image we will be installing the binary dependencies.

To accomplish this we are going to use a Dockerfile:

  1. # Dockerfile
  2. FROM crystallang/crystal:latest
  3. # install binary dependencies:
  4. RUN apt-get update && apt-get install -y libsqlite3-dev

And here is the Travis CI configuration file:

  1. # .travis.yml
  2. language: bash
  3. services:
  4. - docker
  5. before_install:
  6. # build image using Dockerfile:
  7. - docker build -t testing .
  8. script:
  9. # run specs in the container
  10. - docker run -v $PWD:/src -w /src testing crystal spec

Note: Dockerfile arguments can be used to use the same Dockerfile for latest, nightly or a specific version.

Using services

Travis CI may start services as requested.

For example, we can start a MySQL database service by adding a services: section to our .travis.yml:

  1. # .travis.yml
  2. language: crystal
  3. crystal:
  4. - latest
  5. services:
  6. - mysql
  7. script:
  8. - crystal spec

Here is the new test file for testing against the database:

  1. # spec/simple_db_spec.cr
  2. require "./spec_helper"
  3. require "mysql"
  4. it "connects to the database" do
  5. DB.connect ENV["DATABASE_URL"] do |cnn|
  6. cnn.query_one("SELECT 'foo'", as: String).should eq "foo"
  7. end
  8. end

When pushing this changes Travis CI will report the following error: Unknown database 'test' (Exception), showing that we need to configure the MySQL service and also setup the database:

  1. # .travis.yml
  2. language: crystal
  3. crystal:
  4. - latest
  5. env:
  6. global:
  7. - DATABASE_NAME=test
  8. - DATABASE_URL=mysql://root@localhost/$DATABASE_NAME
  9. services:
  10. - mysql
  11. before_install:
  12. - mysql -e "CREATE DATABASE IF NOT EXISTS $DATABASE_NAME;"
  13. - mysql -u root --password="" $DATABASE_NAME < db/schema.sql
  14. script:
  15. - crystal spec

We are using a schema.sql script to create a more readable .travis.yml. The file ./db/schema.sql looks like this:

  1. -- schema.sql
  2. CREATE TABLE ... etc ...

Pushing these changes will trigger Travis CI and the build should be successful!

Caching

If we read Travis CI job log, we will find that every time the job runs, Travis CI needs to fetch the libraries needed to run the application:

  1. Fetching https://github.com/crystal-lang/crystal-mysql.git
  2. Fetching https://github.com/crystal-lang/crystal-db.git

This takes time and, on the other hand, these libraries might not change as often as our application, so it looks like we may cache them and save time.

Travis CI uses caching to improve some parts of the building path. Here is the new configuration file with cache enabled:

  1. # .travis.yml
  2. language: crystal
  3. crystal:
  4. - latest
  5. cache: shards
  6. script:
  7. - crystal spec

Let’s push these changes. Travis CI will run, and it will install dependencies, but then it will cache the shards cache folder which, usually, is ~/.cache/shards. The following runs will use the cached dependencies.