Skip to content

Commit 321a6b5

Browse files
ftarullabcardiffstraight-shoota
authored
New Continuous Integration section (#378)
* New Continuous Integration section Co-authored-by: Brian J. Cardiff <[email protected]> Co-authored-by: Johannes Müller <[email protected]>
1 parent 7b069de commit 321a6b5

File tree

3 files changed

+363
-0
lines changed

3 files changed

+363
-0
lines changed

SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,5 @@
130130
* [Writing Shards](guides/writing_shards.md)
131131
* [Hosting on GitHub](guides/hosting/github.md)
132132
* [Hosting on GitLab](guides/hosting/gitlab.md)
133+
* [Continuous Integration](guides/continuous_integration.md)
134+
* [Using TravisCI](guides/ci/travis.md)

guides/ci/travis.md

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Travis CI
2+
3+
In this section we are going to use [Travis CI](https://travis-ci.org/) as our continuous-integration service. Travis CI is [mostly used](https://docs.travis-ci.com/user/tutorial/#more-than-running-tests) for building and running tests for projects hosted at GitHub. It supports [different programming laguages](https://docs.travis-ci.com/user/tutorial/#selecting-a-different-programming-language) and for our particular case, it supports the [Crystal language](https://docs.travis-ci.com/user/languages/crystal/).
4+
5+
>**Note:**If you are new to continuous integration (or you want to refresh the basic concepts) we may start reading the [core concepts guide](https://docs.travis-ci.com/user/for-beginners/).
6+
7+
Now let's see some examples!
8+
9+
## Build and run specs
10+
11+
### Using `latest` and `nightly`
12+
13+
A first (and very basic) Travis CI config file could be:
14+
15+
```yaml
16+
# .travis.yml
17+
language: crystal
18+
```
19+
20+
That's it! With this config file, Travis CI by default will run `crystal spec`.
21+
Now, we just need to go to Travis CI dashboard to [add the GitHub repository](https://docs.travis-ci.com/user/tutorial/#to-get-started-with-travis-ci).
22+
23+
Let's see another example:
24+
25+
```yaml
26+
# .travis.yml
27+
language: crystal
28+
29+
crystal:
30+
- latest
31+
- nightly
32+
33+
script:
34+
- crystal spec
35+
- crystal tool format --check
36+
```
37+
38+
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.
39+
40+
**Note:** When [creating a Crystal project](../../using_the_compiler/#creating-a-crystal-project) using `crystal init`, Crystal creates a `.travis.yml` file for us.
41+
42+
### Using a specific Crystal release
43+
44+
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](https://github.com/crystal-lang/crystal/releases/tag/0.31.1).
45+
46+
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](https://www.docker.com/).
47+
48+
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:
49+
50+
```yml
51+
# .travis.yml
52+
language: minimal
53+
54+
services:
55+
- docker
56+
57+
script:
58+
- docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 crystal spec
59+
```
60+
61+
**Note:** We may read about different (languages)[https://docs.travis-ci.com/user/languages/] supported by Travis CI, included [minimal](https://docs.travis-ci.com/user/languages/minimal-and-generic/).
62+
63+
**Note:** A list with the different official [Crystal docker images](https://hub.docker.com/r/crystallang/crystal/tags) is available at [DockerHub](https://hub.docker.com/r/crystallang/crystal).
64+
65+
### Using `latest`, `nightly` and a specific Crystal release all together!
66+
67+
Supported _runners_ can be combined with Docker-based _runners_ using a [Build Matrix](https://docs.travis-ci.com/user/customizing-the-build#build-matrix). This will allow us to run tests against `latest` and `nightly` and pinned releases.
68+
69+
Here is the example:
70+
71+
```yaml
72+
# .travis.yml
73+
matrix:
74+
include:
75+
- language: crystal
76+
crystal:
77+
- latest
78+
script:
79+
- crystal spec
80+
81+
- language: crystal
82+
crystal:
83+
- nightly
84+
script:
85+
- crystal spec
86+
87+
- language: bash
88+
services:
89+
- docker
90+
script:
91+
- docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 crystal spec
92+
```
93+
94+
## Installing shards packages
95+
96+
In native _runners_ (`language: crystal`), Travis CI already automatically installs shards dependencies using `shards install`. To improve build performance we may add [caching](#caching) on top of that.
97+
98+
#### Using Docker
99+
100+
In a Docker-based _runner_ we need to run `shards install` explicitly, like this:
101+
102+
```yml
103+
# .travis.yml
104+
language: bash
105+
106+
services:
107+
- docker
108+
109+
script:
110+
- docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 shards install
111+
- docker run -v $PWD:/src -w /src crystallang/crystal:0.31.1 crystal spec
112+
```
113+
114+
**Note:** Since the shards will be installed in `./lib/` folder, it will be preserved for the second docker run command.
115+
116+
## Installing binary dependencies
117+
118+
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](https://help.ubuntu.com/lts/serverguide/apt.html) command (since the Docker image we are using is based on Ubuntu)
119+
120+
Here is a first example installing the `libsqlite3` development package using the [APT addon](https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-with-the-apt-addon):
121+
122+
```yaml
123+
# .travis.yml
124+
language: crystal
125+
crystal:
126+
- latest
127+
128+
before_install:
129+
- sudo apt-get -y install libsqlite3-dev
130+
131+
addons:
132+
apt:
133+
update: true
134+
135+
script:
136+
- crystal spec
137+
```
138+
139+
#### Using Docker
140+
141+
We are going to build a new docker image based on [crystallang/crystal](https://hub.docker.com/r/crystallang/crystal/), and in this new image we will be installing the binary dependencies.
142+
143+
To accomplish this we are going to use a [Dockerfile](https://docs.docker.com/engine/reference/builder/):
144+
145+
```dockerfile
146+
# Dockerfile
147+
FROM crystallang/crystal:latest
148+
149+
# install binary dependencies:
150+
RUN apt-get update && apt-get install -y libsqlite3-dev
151+
```
152+
153+
And here is the Travis CI configuration file:
154+
155+
```yml
156+
# .travis.yml
157+
language: bash
158+
159+
services:
160+
- docker
161+
162+
before_install:
163+
# build image using Dockerfile:
164+
- docker build -t testing .
165+
166+
script:
167+
# run specs in the container
168+
- docker run -v $PWD:/src -w /src testing crystal spec
169+
```
170+
171+
**Note:** Dockerfile arguments can be used to use the same Dockerfile for latest, nightly or a specific version.
172+
173+
## Using services
174+
175+
Travis CI may start [services](https://docs.travis-ci.com/user/database-setup/) as requested.
176+
177+
For example, we can start a [MySQL](https://docs.travis-ci.com/user/database-setup/#mysql) database service by adding a `services:` section to our `.travis.yml`:
178+
179+
```yaml
180+
# .travis.yml
181+
language: crystal
182+
crystal:
183+
- latest
184+
185+
services:
186+
- mysql
187+
188+
script:
189+
- crystal spec
190+
```
191+
192+
Here is the new test file for testing against the database:
193+
194+
```crystal
195+
# spec/simple_db_spec.cr
196+
require "./spec_helper"
197+
require "mysql"
198+
199+
it "connects to the database" do
200+
DB.connect ENV["DATABASE_URL"] do |cnn|
201+
cnn.query_one("SELECT 'foo'", as: String).should eq "foo"
202+
end
203+
end
204+
```
205+
206+
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**:
207+
208+
```yaml
209+
# .travis.yml
210+
language: crystal
211+
crystal:
212+
- latest
213+
214+
env:
215+
global:
216+
- DATABASE_NAME=test
217+
- DATABASE_URL=mysql://root@localhost/$DATABASE_NAME
218+
219+
services:
220+
- mysql
221+
222+
before_install:
223+
- mysql -e "CREATE DATABASE IF NOT EXISTS $DATABASE_NAME;"
224+
- mysql -u root --password="" $DATABASE_NAME < db/schema.sql
225+
226+
script:
227+
- crystal spec
228+
```
229+
230+
We are [using a `schema.sql` script](https://andidittrich.de/2017/06/travisci-setup-mysql-tablesdata-before-running-tests.html) to create a more readable `.travis.yml`. The file `./db/schema.sql` looks like this:
231+
232+
```sql
233+
-- schema.sql
234+
CREATE TABLE ... etc ...
235+
```
236+
237+
Pushing these changes will trigger Travis CI and the build should be successful!
238+
239+
## Caching
240+
241+
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:
242+
243+
```log
244+
Fetching https://github.com/crystal-lang/crystal-mysql.git
245+
Fetching https://github.com/crystal-lang/crystal-db.git
246+
```
247+
248+
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.
249+
250+
Travis CI [uses caching](https://docs.travis-ci.com/user/caching/) to improve some parts of the building path. Here is the new configuration file **with cache enabled**:
251+
252+
```yml
253+
# .travis.yml
254+
language: crystal
255+
crystal:
256+
- latest
257+
258+
cache: shards
259+
260+
script:
261+
- crystal spec
262+
```
263+
264+
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.

guides/continuous_integration.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Continuous Integration
2+
3+
The ability of having immediate feedback on what we are working should be one of the most important characteristics in software development. Imagine making one change to our source code and having to wait 2 weeks to see if it broke something? oh! That would be a nightmare! For this, Continuous Integration will help a team to have immediate and frequent feedback about the status of what they are building.
4+
5+
Martin Fowler [defines Continuous Integration](https://www.martinfowler.com/articles/continuousIntegration.html) as
6+
_a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly._
7+
8+
In the next subsections, we are going to present 2 continuous integration tools: [Travis CI](https://travis-ci.org/) and [Circle CI](https://circleci.com/) and use them with a Crystal example application.
9+
10+
These tools not only will let us build and test our code each time the source has changed but also deploy the result (if the build was successful) or use automatic builds, and maybe test against different platforms, to mention a few.
11+
12+
## The example application
13+
14+
We are going to use Conway's Game of Life as the example application. More precisely, we are going to use only the first iterations in [Conway's Game of Life Kata](http://codingdojo.org/kata/GameOfLife/) solution using [TDD](https://martinfowler.com/bliki/TestDrivenDevelopment.html).
15+
16+
Note that we won't be using TDD in the example itself, but we will mimic as if the example code is the result of the first iterations.
17+
18+
Another important thing to mention is that we are using `crystal init` to [create the application](../using_the_compiler/#creating-a-crystal-project).
19+
20+
And here's the implementation:
21+
22+
```crystal
23+
# src/game_of_life.cr
24+
class Location
25+
getter x : Int32
26+
getter y : Int32
27+
28+
def self.random
29+
Location.new(Random.rand(10), Random.rand(10))
30+
end
31+
32+
def initialize(@x, @y)
33+
end
34+
end
35+
36+
class World
37+
@living_cells : Array(Location)
38+
39+
def self.empty
40+
new
41+
end
42+
43+
def initialize(living_cells = [] of Location)
44+
@living_cells = living_cells
45+
end
46+
47+
def set_living_at(a_location)
48+
@living_cells << a_location
49+
end
50+
51+
def is_empty?
52+
@living_cells.size == 0
53+
end
54+
end
55+
```
56+
57+
And the specs:
58+
59+
```crystal
60+
# spec/game_of_life_spec.cr
61+
require "./spec_helper"
62+
63+
describe "a new world" do
64+
it "should be empty" do
65+
world = World.new
66+
world.is_empty?.should be_true
67+
end
68+
end
69+
70+
describe "an empty world" do
71+
it "should not be empty after adding a cell" do
72+
world = World.empty
73+
world.set_living_at(Location.random)
74+
world.is_empty?.should be_false
75+
end
76+
end
77+
```
78+
79+
And this is all we need for our continuous integration examples! Let's start!
80+
81+
## Continuous Integration step by step
82+
83+
Here's the list of items we want to achieve:
84+
85+
1. Build and run specs using 3 different Crystal's versions:
86+
* latest
87+
* nightly
88+
* 0.31.1 (using a Docker image)
89+
2. Install shards packages
90+
3. Install binary dependencies
91+
4. Use a database (for example MySQL)
92+
5. Cache dependencies to make the build run faster
93+
94+
From here choose your next steps:
95+
96+
* I want to use [Travis CI](./ci/travis.html)
97+
* I want to use Circle CI (coming soon...)

0 commit comments

Comments
 (0)