Photo credit: Unsplash: _louisreed
Recently, I started a new side project (maybe more on that soon), and I decided to give GitLab a try. I’ve used it for some client work through my job, but had never really messed with it on my own.
Some of my motivations for giving it a try:
- Free Private Repos
- Issue management with a Kanban board
- Milestones
- CI / CD built in
The biggest reason here is the free hours of Continuous Integration (CI). There are several guides for setting up testing for an Elixir Project, but my setup ended up a little different, so I figured I’d throw up a post about it (and have it as a reference for myself, for the future, one of the best reasons to blog!)
GitLab CI Config
Everything starts with a .gitlab-ci.yml
file:
image: elixir:1.7.3
services:
- postgres:9.6
variables:
POSTGRES_DB: app_name_test
POSTGRES_HOST: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: "postgres"
MIX_ENV: "test"
This part is pretty straight forward, just set the running to use the right version of Elixir, connect PostgreSQL (if needed) and set up a few global variables
test.exs
use Mix.Config
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :app_name, Web.Endpoint,
http: [port: 4001],
server: false
# Print only warnings and errors during test
config :logger, level: :warn
# Configure your database
config :app_name, AppName.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("POSTGRES_USER") || "postgres",
password: System.get_env("POSTGRES_PASSWORD") || "postgres",
database: System.get_env("POSTGRES_DB") || "app_name_test",
hostname: System.get_env("POSTGRES_HOST") || "localhost",
pool: Ecto.Adapters.SQL.Sandbox
This is set up to mostly just pull the env variables from the CI run, or default to something reasonable so that your tests will still run locally.
Global Setup
Next up in the .gitlab-ci.yml
file:
before_script:
- mix local.hex --force
- mix local.rebar --force
Here we are making sure that every job that runs (each stage of the pipeline) has hex and rebar ready to go. This is important when we add caching later, as these install outside the project directory.
Stages
Now we will add stages to the .gitlab-ci.yml
file:
compile:
stage: build
script:
- apt-get update && apt-get -y install postgresql-client
- mix deps.get --only test
- mix compile --warnings-as-errors
test:
stage: test
script:
- mix ecto.create
- mix ecto.migrate
- mix test
lint:
stage: test
script:
- mix format --check-formatted
- mix credo
The setup here runs the compile job in the build stage, so this will happen first. We’ll make sure we are ready with the postgres client, all the dependices for the app, and we’ll compile all our elixir code. I set --warnings-as-errors
to make sure I’m not leaving anything deprecated or unused behind in my code.
The test stage has two jobs, test and lint. These will be able to run in parallel on GitLab’s servers, or any other connected runner. The test job setup up ecto, and runs the test suite. The link job makes sure everything is formatted, and clears a credo check.
Here is the display from GitLab for the pipeline stages:
And the pipeline jobs:
Caching
Lastly, we want builds to go faster, so we add a block for caching configuration:
cache:
paths:
- _build
- deps
- assets/node_modules
This way our dependecy download, build output and anything with our node modules in assets are preserved and not built from scratch each time. Mix and Yarn should handle if your dependencies need updating due to a change you’ve made.
Conclusion
This approach seems to be working great! For my really simple starting Phoenix app with just a few tests, the original build took about 4.5 minutes, and each stage now runs in about 1.75 minutes. That is of course a lot more time than the tests take to run themselves, but there is a lot of overhead to get the tests ready to run. The formatting and credo runs (and tests and compile without warnings) I also do myself with git pre-commit hooks (I should do a post on that), so its unlikely something wrong would slip in, but I like the redundancy, and having this automated is a lot of fun!
Here is the GitLab interface for a successful pipeline run:
Happy CI-ing!
Here is the whole file for reference:
image: elixir:1.7.3
services:
- postgres:9.6
variables:
POSTGRES_DB: app_name_test
POSTGRES_HOST: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: "postgres"
MIX_ENV: "test"
cache:
paths:
- _build
- deps
- assets/node_modules
before_script:
- mix local.hex --force
- mix local.rebar --force
compile:
stage: build
script:
- apt-get update && apt-get -y install postgresql-client
- mix deps.get --only test
- mix compile --warnings-as-errors
test:
stage: test
script:
- mix ecto.create
- mix ecto.migrate
- mix test
lint:
stage: test
script:
- mix format --check-formatted
- mix credo