Common LispプロジェクトをTravis CIで継続的インテグレーション

1. 継続的インテグレーション
=======================

__継続的インテグレーション__とは、一般的に「プログラムのビルドやテストを継続的に行うことでプログラムの品質を向上させる習慣」のことのようです。この記事で紹介したい「__継続的インテグレーション__」とは「GitHubにあるCLプロジェクトに対する、PushやPull Requestをトリガーとして実行される、自動ビルド＆テスト」のことです。この記事内では「__自動テスト__」という単語を「継続的インテグレーション」と同じものとして使用します。`(setf 自動テスト 継続的インテグレーション)`


## オープンソースプロジェクトでの継続的インテグレーションの利点

1.プロジェクトの開発者や管理者から見ると、

- 誤ったPushを行なってしまったときに、すぐにわかる。
- Pull Requestを頂戴したときに、マージしても既存のプログラムを破壊しないか、すぐにわかる。

2.プロジェクトに貢献したい人から見ると、

- Pull Requestを送る敷居が多少下がる。

3.プロジェクトのユーザから見ると、

- レポジトリが健全であるか、一目瞭然となる。

4.多人数の開発では、

- 以下のような手順を開発サイクルの単位とする開発方法を助けてくれる（参考：[GitHub Flow](https://gist.github.com/Gab-km/3705015)）。

    ブランチ作成 -> Commit -> .. -> Commit -> Push -> Pull Request -> レビュー -> Merge


## Travis CIについて

この記事では継続的インテグレーションを行うためにTravis CIを利用します。
「[Travis CI](https://travis-ci.org/)」って何だ？という方は、次の記事が概要をつかむのに良いでしょう。

- [Travis CI 入門：GitHub + Travis CI で継続的インテグレーション](http://tetsuwo.tumblr.com/post/44706350593/github-travis-continuous-integration)

また、Jenkinsを用いた継続的インテグレーションについては、深町さんのブログが参考になるはずです。

- [Common LispプロジェクトをJenkinsで継続的インテグレーション](http://blog.8arrow.org/entry/20111027/1319718942)


2. マイナー言語の悲哀
==================

いきなりですが、残念なことにCommon LispはTravis CIで[__ファーストクラスにサポートされていない__](http://docs.travis-ci.com/user/getting-started/)言語です。Common Lispプロジェクトをテストする環境（CL処理系やQuicklisp等）は、自分でTravis CI内に構築しなければなりません。以降の章でテスト実行環境を構築する方法を２つ紹介します。

 - apt-getを用いる方法：単一の処理系だけでテストできれば十分な場合に適している。
 - cl-travisを用いる方法：多数の異なる処理系でテストしたい場合に適している。

もし、Travis CIの使い方をご存知で、お急ぎの場合には[cl-travisを用いる方法](#cl-travis)を先にお読み下さい。


3. apt-getによるテスト環境構築
===========================

単一の処理系のみでテストが実行できれば十分で、さらに、その処理系がapt-getでインストールできるならば、これから紹介するテスト環境の構築方法が一番シンプルでしょう。処理系のバージョンは最新のものではありませんが、Travis CIの[仮想マシン環境](http://docs.travis-ci.com/user/ci-environment/)では安定したもののはずです。複数の処理系でテストを実行したい場合には、Quicklispのホームディレクトリ情報の受け渡しのために、やや複雑になってしまいます（複数の処理系の場合は[後述のcl-travis](#cl-travis)を用いたほうが楽です）。ここでは、処理系として__sbcl__のみを用いたテスト環境を構築していきます。


### Travis CIが行う自動テストの流れ：

1. apt-getでsbclをインストール。
2. quicklisp.lispをダウンロード。
3. Quicklispをインストールしテストを開始する、lispスクリプトを実行する。

（[参考例：Travis CI/intro-travis](https://travis-ci.org/tkych/intro-travis/builds/31146625)）


### 上の自動テストの仕組みを整備するために：

1. 自動テストを整備するためのトピックブランチを作成する。
1. テストを実行するためのメイン関数をテストパッケージに追加する。
1. テスト実行スクリプトをレポジトリに追加する。
1. .travis.ymlファイルをレポジトリに追加する。
1. Travis CIでGitHubレポジトリに対する設定をONにする。
1. トピックブランチからPull Requestを作成する。
1. テスト結果を確認してMergeする。

以下で、これらを詳しく解説していきます。

（[参考例：GitHub/intro-travis](https://github.com/tkych/intro-travis/pull/1)）

## 3.0 下準備

- プロジェクトにテストシステムを整備しておく（例えば、fooプロジェクトに対しfoo-test.asdファイルとfoo-testパッケージ）。
- プロジェクトのレポジトリをGithubに置いておく。
- レポジトリがクリーンであること（コミット待ちの変更などがないこと）を確認する。


## 3.1 トピックブランチの作成

新たにブランチを作成します。これはTravis CIによる自動テストの整備が目的のトピックブランチです。最終的に、このブランチからPull Requestを作成し、自動テストが正しく動作するか確認します。ここでは名前を仮に「travis-ci」としましょう（すでに同名のブランチが存在する場合は適当な名前に変えて下さい）。

    $ git checkout -b travis-ci

以下はtravis-ciブランチで作業します。


## 3.2 テストのためのメイン関数

テストのためのメイン関数をテストシステムに追加しましょう。ここでは名前を仮に`RUN-TESTS`とします（すでに同名の関数が存在する場合は適当な名前に変えて下さい）。`RUN-TESTS`は失敗時に`NIL`、成功時に`T`を返すものとします。Travis CIが、テストの結果を判断するために必要なことは、`RUN-TESTS`が２値関数であることです（より正確には、定数関数でないことです）。

ここではユニットテストのためのライブラリ、[FiveAM](http://common-lisp.net/project/fiveam/)でテストを行います。

「FiveAM」って何だ？という方は、以下の、l_libraさんとg000001さんの解説が参考になるはずです。

- [FiveAM：主にl_libraさんの解説](http://cl.cddddr.org/index.cgi?FiveAM)
- [fiveam：g000001さんの解説](http://qiita.com/g000001/items/c02a549eb80b7d9c30a7)

ちなみに、FiveAM以外のライブラリでテストしたい場合には、以下のレポジトリの方法が参考になるはずです。

 - [CL-TEST-MORE: cl-mustache](https://github.com/kanru/cl-mustache)
 - [LISP-UNIT: cletris](https://github.com/nlamirault/cletris)
 - [RT: cl-travis](https://github.com/luismbo/cl-travis)


さて、FiveAMで`RUN!`によって起動されるテストは結果にかかわらず常に`NIL`を返します。このままでは返り値から、Travis CIがテストの成功失敗を判断することができません。そこで、テストシステムのためのパッケージ（例えば`FOO-TEST`）で`RUN-TESTS`を次のように定義し、エクスポートしておきます。

```lisp
(defun run-tests ()
  (let* ((result-list  (fiveam:run '?FOO-TEST-ALL)) ; ?FOO-TEST-ALLは、適切なテストスイート名に変更して下さい。
         (total-result (every (lambda (r) (typep r 'fiveam::test-passed))
                              result-list)))
    (fiveam:explain! result-list)
    total-result))
```

`RUN-TESTS`を追加したら、忘れずにtravis-ciブランチでコミットしておきましょう。

    $ git commit -m "Add function RUN-TESTS"


## 3.3　テスト実行スクリプトの追加

Travis CIがテストを開始するためのスクリプトを作成しましょう。ここでは名前を仮に`run-tests.lisp`とします（すでに同名のファイルが存在する場合は適当な名前に変えて下さい）。内容は次のようになります。

```lisp
#!/usr/bin/sbcl --script
    
(load "quicklisp.lisp")
(quicklisp-quickstart:install)

(ql:quickload :foo-test)

(if (foo-test:run-tests)
    (sb-ext:quit :unix-status 0)
    (sb-ext:quit :unix-status 1))
```

一行目はlispファイルをスクリプトとして実行するためのシバンです。はじめの２つのフォームでQuicklispをインストールします。３つめのフォームでテストシステムをLispにロードします。最後のフォームでテストを実行し、シェルスクリプトの終了ステータスとして、テスト成功の場合は`0`、失敗の場合は`1`を返します。これにより、Travis CIがテスト結果を判断します。

run-tests.lispファイルの追加後、忘れずにtravis-ciブランチでコミットしておきましょう。

    $ git commit -m "Add run-tests.lisp"


## 3.4 .travis.ymlファイルの追加

.travis.ymlファイルをプロジェクトレポジトリのルートに追加しましょう。このファイルは、Travis CIによる自動テストに必須のもので、テスト方法を細かく設定することができます。ファイルの内容は以下のようになります。


    language: lisp

    install:
     - sudo apt-get update -qq
     - sudo apt-get install -qq sbcl
     - curl --no-progress-bar --retry 10 -o quicklisp.lisp -L http://beta.quicklisp.org/quicklisp.lisp

    script:
     - sbcl --script test/run-tests.lisp

    notifications:
      email:
        - bar@example.com


`install:`でsbclのインストールとQuicklispのダウンロードします。

`script:`でスクリプトを実行します（Quicklispをインストールし、テストシステムをロードし、テストを実行）。テスト実行スクリプトrun-tests.lispは、プロジェクトルートにあるtestディレクトリにあるとしています。

`notifications:`でメールアドレスを設定しておけば、Travis CIがテスト結果を直接、通知してくれるようになります。通知方法はメール以外にもIRCやwebhookなど色々設定できるようです（通知についての詳しい設定方法は[Travis/docs/notifications](http://docs.travis-ci.com/user/notifications/)をご覧ください）。

.travis.ymlファイルを書き上げたら[Travis WebLint](http://lint.travis-ci.org/)で形式に問題がないか確認してみましょう。いきなり、`language: lisp`は__"illegal"__だよと言われます（`lisp`は__非合法__ってなんだかすごいですね）。しかし、そこはあえて無視しましょう。他の部分が__"legal"__なら大丈夫です。レポジトリのルートに.travis.ymlを追加しましょう。ブランチがtravis-ciであることを確認してコミットするべし！（エンターキーは内角をねらい、えぐりこむようにして打つべし！）

    $ git commit -m "Add .travis.yml"


## 3.5 Travis CIアカウントでレポジトリの設定

Travis CI アカウントでGitHubとの連携を設定します。まず、Travis CIサイトの右上にある「Sign in with GitHub」からGitHub経由でログインしましょう。つぎに「Accounts」に移動し、リポジトリ一覧内で自動テストを実行したいプロジェクト名の右側にあるスイッチを「ON」にします。いよいよ自動テスト起動開始の刻（とき）が近づいてきました。


## 3.6 Pull Requestの作成

travis-ciブランチからPull Requestを作成します。

まず、次のようにPushします（注：travis-ciブランチのレポジトリには.travis.ymlとrun-tests.lispが追加されコミット済みであること）。

    $ git push -u origin travis-ci


次に、GitHubにログインし、レポジトリのページを確認しましょう。上の方に「Compare & pull request」というボタンが表示されているはずです。それをクリックし、travis-ciブランチからPull Requestを作成しましょう。このPull Requestをトリガーとして、Travis CIで自動テストが開始されます。


## 3.7 テスト結果の確認

テストが開始するまでには、Travis CIの込み具合によっては多少時間がかかるかもしれません。
テストの結果はPull Requestの画面に表示されます。

テスト成功ならmasterブランチにマージしてもOKです。
これで自動テストの設定は完了です。

テスト失敗の場合、何らかの不具合が考えられます。
自動テストのログをTravis CIのレポジトリのページ（以下のurl）で確認しましょう。

    https://travis-ci.org/[YOUR-GITHUB-ACCOUNT-NAME]/[YOUR-PROJECT-NAME]


不具合の原因と考えられるものは、おそらく以下のどちらかでしょう。

1. レポジトリにあるプログラムが、そもそもテストを通さないものであった。-> プログラムもしくはテストの修正が必要。
2. 自動テスト設定を間違った。-> travis-ciブランチでRUN-TESTS、.travis.ymlまたはrun-tests.lispの修正が必要。


また、自動テストを手動で実行するには、以下のどちらかで行うことができます。

1. GitHubのレポジトリのページの「Settings」->「Webhooks&Services」->　Travis CIの「Editマーク」-> 右上にある「Test service」をクリック
2. Travis CIのレポジトリのページの右上から３段くらい下にある「再読み込みマーク」のクリック


テストが開始するまでには、Travis CIの込み具合によっては多少時間がかかるかもしれません（大事なことなのでもう一度書きました）。気長に待ちましょう。テストが開始しないからといって、決して「Test service」ボタンを連打してはいけません。同じテストが何度も実行されることになってしまいます（失敗例：https://travis-ci.org/tkych/cl-plus/builds）。


## 3.8 テスト結果をREADMEファイルに表示

[![Build Status](https://travis-ci.org/tkych/cl-spark.svg?branch=master)](https://travis-ci.org/tkych/cl-spark)

最後に、上のようなテスト結果をREADMEファイルの目立つ部分に表示しましょう。Markdown形式のREADMEファイルに表示するには、次の一行をREADMEファイルに追加するだけでOKです。

    [![Build Status](https://travis-ci.org/[YOUR-GITHUB-ACCOUNT-NAME]/[YOUR-PROJECT-NAME].svg?branch=master)](https://travis-ci.org/[YOUR-GITHUB-ACCOUNT-NAME]/[YOUR-PROJECT-NAME])

（詳しくは[Travis/docs/status-images](http://docs.travis-ci.com/user/status-images/)をご覧ください）

これにより、

1. 自分がテスト結果を知ることができ、
2. Travis CIを利用していますよ、と世の中にアピールすることができ、
3. かっこいい。

結果、継続的インテグレーションの利点を、フルに享受することができるようになります。


4. cl-travisによるテスト環境構築 <a name="cl-travis">
=============================

### Travis CIが行う自動テストの流れ：

1. cl-travisをダウンロードし実行する。
2. 各処理系ごとに、cl-launch でテストフォームを実行する。

（[参考例：Travis CI/intro-travis](https://travis-ci.org/tkych/intro-travis/builds/31147400)）

### 自動テストの仕組みを整備するために：

1. 自動テストを整備するためのトピックブランチを作成する。
1. テストを実行するためのメイン関数をテストパッケージに追加する。
1. .travis.ymlファイルをレポジトリに追加する。
1. Travis CIアカウントでGitHubレポジトリに対する設定をONにする。
1. トピックブランチからPull Requestを作成する。
1. テスト結果を確認し、Mergeする。

（[参考例：GitHub/intro-travis](https://github.com/tkych/intro-travis/pull/2)）

## cl-travisとは何か

[cl-travis](https://github.com/luismbo/cl-travis)とは、[slime](https://github.com/slime/slime)の開発者でもある[Luís Oliveira](https://github.com/luismbo)さん作のライブラリ？で、Common LispでTravis CIを利用するためものです。ライセンスは[issue#2](https://github.com/luismbo/cl-travis/issues/2)によるとMITライセンスです。ちなみに、slimeはcl-travisを用いて[テストされている](https://travis-ci.org/slime/slime)ようです。

cl-travisの主な使用法は文章化されていませんが、slimeのテスト方法を見た限りでは、cl-travis/install.shの直接ダウンロード、実行のようです。cl-travisがやってくれることは、CL処理系各種と、Quicklispと、cl-launchをテスト環境にインストールすることです。

ここで「[cl-launch](http://cliki.net/cl-launch)」は[François-René Rideau](https://github.com/fare)さん作のライブラリ？で、Common Lispでポータブルなシェルスクリプトの生成や、コマンドラインでREPLのようにLispフォームを実行するためのものです。さらに詳しく知りたい方は、次のサイトが参考になるはずです。

- [ASDF 3, or Why Lisp is Now an Acceptable Scripting Language](http://fare.tunes.org/files/asdf3/asdf3-2014.html)


## cl-travisのための.travis.ymlファイル

.travis.ymlファイルの内容以外は、前述のapt-getを用いた方法と同様ですので省略します。
.travis.ymlファイルの内容をざっと見て行きましょう。


    language: lisp

    env:
      matrix:
        - LISP=abcl
        - LISP=sbcl
        - LISP=sbcl32
        - LISP=ccl
        - LISP=ccl32
        - LISP=clisp
        - LISP=clisp32
        - LISP=cmucl
        - LISP=ecl

    install:
     - curl --no-progress-bar --retry 10 https://raw2.github.com/luismbo/cl-travis/master/install.sh | bash

    script:
     - cl-launch -i "(ql:quickload :foo-test) (uiop:quit (if (foo-test:run-tests) 0 1))"

    notifications:
      email:
        - bar@example.com


`matrix:`は、複数の仮想マシンで並列にテストを実行するためのものです（参考：[Travis/docs/speeding-up-the-build](http://docs.travis-ci.com/user/speeding-up-the-build/)）。

`install:`では、cl-travisをダウンロードし、CL処理系各種と、Quicklispと、cl-launchをテスト環境にインストールします。

`script:`では、テストシステムをロードし、テストを実行し、その結果を終了ステータスでTravis CIに通知します。

`uiop`（the Utilities for Implementation- and OS- Portability）は、その名が示すようにポータブルなプログラムを書くためのユーティリティ集で、asdf3に含まれています。くわしくは[uiopのREADME](http://common-lisp.net/gitweb?p=projects/asdf/asdf.git;a=blob;f=uiop/README;hb=HEAD;js=1)をご覧ください。


5. 今後の展望
===========

- cl-travisでは処理系のバージョンなどを細かく指定できません。KeenSさんが開発した（or 開発中の）[CIM（Common Lisp Implementation Manager）](https://github.com/KeenS/CIM)に期待してます。
- CIMを利用する場合、ひょっとしたら、深町さん開発中の[shelly](https://github.com/fukamachi/shelly)の方がcl-launchよりも相性がよいかもしれません。
- （私の知る限り）Quicklispではライブラリのバージョンを指定することは指定できません。Mariano Montoneさん開発中の[cldm（A Common Lisp Dependency Manager）](https://github.com/cldm/cldm)に期待してます。
- __追記(7/29)：__ 深町さん開発中の[qlot](https://github.com/fukamachi/qlot)でライブラリのバージョン管理が容易になりそうです。