How to 1/x: Fast and Stable tests for sanity and profit

RUG::B Sep 2018, by @zalesz

@zalesz

RUG::B Sep 2018, by @zalesz

Development Lead at

Agenda

  • What's the problem?
  • Make it faster
  • Track and annihilate randoms

RUG::B Sep 2018, by @zalesz

What's

The

Problem?

RUG::B Sep 2018, by @zalesz

Oh no, not again..

RUG::B Sep 2018, by @zalesz

Couple restarts later..

... now we can deploy and go home

RUG::B Sep 2018, by @zalesz

Long feedback loops

What I was doing hour ago?

Wait for restart to deploy

Our test suite is basically useless

RUG::B Sep 2018, by @zalesz

Need

for

speed!

RUG::B Sep 2018, by @zalesz

Enter knapsack gem

RUG::B Sep 2018, by @zalesz

Installation

# Add gem in Gemfile
group :test, :development do
  gem "knapsack"
end
# Tasks in Rakefile
Knapsack.load_tasks if defined?(Knapsack)
# Setup in spec_helper.rb
if ENV["CI"]
  require "knapsack"
  Knapsack::Adapters::RSpecAdapter.bind
end

RUG::B Sep 2018, by @zalesz

Generate report on CI

# in .travis.yml replace default script with following
script: 
  - "KNAPSACK_GENERATE_REPORT=true bundle exec rspec spec"

RUG::B Sep 2018, by @zalesz

After tests pass you should

  1. Copy report which is rendered at the end of the tests in CI
  2. Save it into your repository as knapsack_rspec_report.json

RUG::B Sep 2018, by @zalesz

After tests pass you should

# 3. Setup .travis.yml to run multiple jobs in parallel

env:
  global:
    - RAILS_ENV=test
    - CI_NODE_TOTAL=4 # total number of workers

  matrix:
    - CI_NODE_INDEX=0
    - CI_NODE_INDEX=1
    - CI_NODE_INDEX=2
    - CI_NODE_INDEX=3

script:
  - "bundle exec rake knapsack:rspec"

RUG::B Sep 2018, by @zalesz

Reap the benefits!

RUG::B Sep 2018, by @zalesz

Reap the benefits!

RUG::B Sep 2018, by @zalesz

Track and annihilate random failures!

RUG::B Sep 2018, by @zalesz

Find the failure

RUG::B Sep 2018, by @zalesz

Find the rspec seed

RUG::B Sep 2018, by @zalesz

Reproduce locally

$ CI_NODE_TOTAL=8 \
  CI_NODE_INDEX=5 \
  CI=true \
  bundle exec rake "knapsack:rspec[--seed 4049]"

RUG::B Sep 2018, by @zalesz

We need to go deeper!

CI_NODE_TOTAL=8 CI_NODE_INDEX=5 CI=true \
bundle exec rake "knapsack:rspec[--seed 4049 --bisect=verbose]"
# => 

Report specs:   # list of files selected by knapsack

Bisect started using options: "--seed 2463 --default-path spec -- 
spec/foo_spec.rb etc"

Running suite to find failures... (5 minutes 0 seconds)
 - Failing examples (3):
    - ./spec/controllers/api/v2/rooms_controller_spec.rb[1:1:1:2:2:1]
    - ./spec/features/attachment_spec.rb[1:2]
    - ./spec/features/workflow_spec.rb[1:1]
 - Non-failing examples (924):
    - rest of the files

Checking that failure(s) are order-dependent..
 - Running: rspec (50.83 seconds)
 - Failure appears to be order-dependent

Round 1: bisecting over non-failing examples 1-924
 - Multiple culprits detected - splitting candidates

Round 2: bisecting over non-failing examples 1-462
...

RUG::B Sep 2018, by @zalesz

Fix The Thing

And repeat :)

RUG::B Sep 2018, by @zalesz

Profit!

25 HOURS saved a day

~50' faster builds

~30 builds a day

RUG::B Sep 2018, by @zalesz

Profit!

Chill & rely on the

tests suite

Keep focused

Faster feedback

RUG::B Sep 2018, by @zalesz

Bonus: Cache CI dependencies

cache:
  bundler: true
  directories:
    - ~/bin

# install only test dependencies
bundler_args: "--without development production"

# use built in tools
addons:
  postgresql: '9.6'
  chrome: stable

before_install:
  - ./bin/ci_install_chromedriver

RUG::B Sep 2018, by @zalesz

Bonus: Cache CI dependencies

#!/bin/bash
set -Eeuo pipefail

VERSION="2.41"
NAME="chromedriver_${VERSION}"
STORAGE=https://chromedriver.storage.googleapis.com

install_chromedriver() {
  rm -f ~/bin/chromedriver*

  wget ${STORAGE}/${VERSION}/chromedriver_linux64.zip

  unzip chromedriver_linux64.zip
  rm chromedriver_linux64.zip
  mv -f chromedriver ~/bin/${NAME}
  chmod +x ~/bin/${NAME}

  ln -s ~/bin/${NAME} ~/bin/chromedriver
}

[ -e ~/bin/${NAME} ] || install_chromedriver

RUG::B Sep 2018, by @zalesz

Thanks!

RUG::B Sep 2018, by @zalesz