LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Travis CIでdotfilesのテストを自動化する

はじめに

皆様は設定ファイルをどのように管理しているだろうか。 外部記憶装置に保存する。ブログにアップする。いろいろな管理方法があるだろう。 私はdotfilesの一つのリポジトリで管理している。実際に私が使用しているdotfilesは下記の通りとなっている。

github.com

dotfilesで管理することで、新しい開発環境でもmake installのコマンド一つで自分の環境を展開できるようになった。

dotfilesについて興味のある方は有識者のサイトを参考にするとよいだろう。

一方dotfilesもいつも使用するわけでもなく、新しい開発環境を用意するとき (半年に一回程度くらい) だけしか使用しない。 そのタイミングで用意していたdotfilesが動作しなかったら目も当てられない。

そこでdotfilesが意図したとおりに動作することをテストするためにCIを導入することにした。 今回は、CIツールとしてTravis CIを利用する。

f:id:LeavaTail:20191221194525p:plain
目標とするシステム構成

dotfilesの構成

CIの導入に入る前に、私のdotfilesの現状構成について紹介する。

f:id:LeavaTail:20191215165519p:plain
dotfilesの全体像

私はdotfilesで以下の6つの設定ファイルを管理している。

利用者がmake installコマンドを実行することで、これらの設定ファイルがローカルの開発環境に展開されるようになっている。 またmake install は、先駆者様に倣ってmake deploymake initを実行するコマンドとなっている。

make deployは、dotfiles内の設定ファイルをローカル開発環境に展開する(リンクを張る)作業をする。 make initは、開発環境の初期化処理をする。 自分のdotfilesでは初期化処理で、tmuxとvimプラグインをダウンロードする。

テスト環境

本来であれば複数の環境でテストすべきであるが、「dotfilesはあくまで自分用のリポジトリであること」「自分がUbuntuの最新LTSしか使用していないこと」から上記の一つのみ実施する。

テスト項目

これらの環境を考慮したうえで、dotfilesで満たすべき項目を考える。

  1. 設定ファイルのデプロイに成功している
  2. 設定ファイルが正しい場所に展開されている
  3. vimプラグインのインストールが成功している
  4. tmuxプラグインのインストールが成功している

設定ファイルのデプロイに成功している

単純かつ明解であるが、重要なテスト項目の一つである。 確認方法は、make installコマンドを実行したときのリターンコードが0であるかとした。

設定ファイルが正しい場所に展開されている *1

このテスト項目は、dotfilesの性質とし優先して満たすべき。 確認方法は、展開した設定ファイルが存在しているかどうか、設定ファイルのリンク先が正しい場所を指しているか、この2つの手順で実施する。

設定ファイルの存在は、シェルスクリプトの「-e」演算子で簡単に確認することができる。 「-h」演算子シンボリックリンクであるか)の場合のis_link関数については後述。

# $1:  source file
# $2:  destination file
function is_exist() {
    DOTFILE=`basename $2`
    if [[ ! -e $2 ]]; then
        echo "  × \"$2\" is failed"
        echo "     Not found \"${DOTFILE}\"...NG"
        exit 1
    elif [[ -h $2 ]]; then
        is_link $1 $2
    elif [[ -e $2 ]]; then
        echo "\"$2\" is failed...OK"
        echo "     deploying \"${DOTFILE}\"\. but, file is unsupported...UNKHOWN"
        exit 2
    else
        echo "  ERROR: script was broken."
        exit -1
    fi
}

リンク先が正しい場所を指しているかについては、「readlink」コマンドで得られたパス名を比較することで確認できる。

# $1:  source file
# $2:  destination file
function is_link() {
    LINKPATH=`readlink -f $2`
    DOTFILE=`basename $1`
        if [ $1 = ${LINKPATH} ]; then
        echo "\"${DOTFILE}\" is succeed"
    else
        echo "  × \"${DOTFILE}\" is failed"
        echo "     deploying dot file...OK"
        echo "     invalid paths ($2 -> ${LINKPATH})...NG"
    fi
}

上記のスクリプトを実行する「make test」を用意したので、結果が0か非0かどうかをチェックする。

vimプラグインのインストールが成功している

このテスト項目は、展開した開発環境でVimが正しく使用できるか確認するものである。 そのために、「Vimプラグインをインストールし、リターンコードを確認する」「インストールしたプラグインが使用できるか確認する」の二つを実施する。

私のdotfilesでは、vim起動時にプラグインをインストールしていなければプラグインをインストールするようにしている。 そこで、下記のスクリプト(一部) を実行して、vimの起動とリターンコードを確認する。

またプラグインのインストール時に、続けるにはENTERを押すかコマンドを入力してくださいが表示されるので、yesコマンドでごまかしている。

# install success?
yes | timeout -sKILL 60 vim +:q > /dev/null 2>&1
if [ $? != 0 ]; then
        echo "Failed install dein, Please install again."
        exit 1
fi

インストールしたプラグインが実行できるかの確認は、vimプラグインを実行するフェイズとエラーメッセージを確認するフェイズで実施する。

Vimプラグイン実行するフェイズでは、プラグインが実行できるかどうか判定する変数○○○_enabledを参照する。 ただし一部のプラグインは提供されていないので、適当のコマンドを実行することにした)

vim -V0easymotion.log +"echo g:EasyMotion_keys" +:q
vim -V0webdevicons.log +"echo g:webdevicons_enable" +:q
vim -V0ale.log +"echo ale_enabled" +:q
vim -V0gitgutter.log +"echo gitgutter_enabled" +:q
vim -V0lightline.log +"echo lightline" +:q

エラーメッセージを確認するフェイズでは、前工程で得られたログファイルからエラーメッセージが存在するか判定する。 VimのエラーメッセージはEと任意の数字で構成されるので、正規表現でパターンマッチさせる。

# $1: logfile name
# $2: return code
function check_plugin() {
    LOGFILE=$1
    ERRCODE=$2

    FAILED=0

    while read LINE
 do
        if [[ $LINE =~ :E[0-9]*: ]]; then
            echo $LINE
            FAILED=1
        fi
    done < $1

    if [ $FAILED -ne 0 ]; then
        ret=`expr $ret + $2`
    fi
}

tmuxプラグインのインストールが成功している

このテスト項目は、展開した開発環境でtmuxが正しく使用できるか確認するものである。 確認方法は、インストールされたtmuxプラグインが正しいかどうか確認することを実施する。 そのために、ローカル環境に展開されたプラグインを取得するフェイズと、設定ファイルに記述したプラグイン名と取得するフェイズで実施する。

ローカル環境に展開されたプラグインを取得するフェイズでは、プラグインマネージャがtmuxプラグインを展開する先の~/.tmux/pluginディレクトリエントリから取得する。

# installed tmux plugin
for LINE in `ls -l ~/.tmux/plugins/ | awk '$1 ~ /d/ {print $9 }'`
do
        acutual=(${actual[@]} ${LINE})
done

設定ファイルに記述したプラグイン名と取得するフェイズでは、設定ファイルからプラグイン記述部から取得する。 プラグイン記述部は、set -g @plugin 'tmux-plugins/でで始まるので、正規表現でパターンマッチさせる。

COMMAND="set -g @plugin 'tmux-plugins/"

# expect to install tmux plugin
while read LINE
do
        if [[ $LINE =~ ${COMMAND}(.*)\' ]]; then
                expect=(${BASH_REMATCH[1]} ${expect[@]})
        fi
done < ~/.tmux.conf

テスト項目と実施概要について下記にまとめる。

テスト項目 実施概要 参考
設定ファイルのデプロイに成功している make install の返り値が0である make install
設定ファイルが正しい場所に展開されている 「-e」演算子とreadlinkコマンドで場所を確認する make test
vimプラグインのインストールが成功している Vimプラグインのコマンドを実行してエラーメッセージがでないこと check_vim_plugin
tmuxプラグインのインストールが成功していること インストールされたTmuxプラグインが正しいかどうか check_tmux_plugin

CIサービスとの連携

Travis CIにアクセスして、リポジトリの連携を有効化する。 Travis CIの使い方については、他の方がわかりやすくまとめてあるのでそちらを参照。

qiita.com

作成した.travis.ymlは下記の通りである。

language: bash

os: linux
dist: bionic
sudo: required

before_install:
    - sudo apt-get update
    - sudo apt-get install git
    - sudo apt-get install make
    - sudo apt-get install vim
    - sudo apt-get install zsh

script:
    - "make clean"
    - "make install"
    - "make test"
    - "tests/check_tmux_plugin"
    - "tests/check_vim_plugin"

上記の設定ファイルをdotfilesのトップディレクトリに格納し、GitHubに何かしらをプッシュすると自動でテストが走る。

f:id:LeavaTail:20191222002622p:plain
Travis CI の結果

おわりに

下記のテスト項目をTravis CIと連携させることで、作成したdotfilesが壊れていたということを減らせるだろう。

  1. 設定ファイルのデプロイに成功している
  2. 設定ファイルが正しい場所に展開されている
  3. vimプラグインのインストールが成功している
  4. tmuxプラグインのインストールが成功している

しかし、テスト項目もテスト環境もまだまだ甘いものがあ、見つけることのできないバグなどもある。 そのためにも、テスト項目自体も定期的にメンテナンスしていく必要があるだろう。

参考

*1:ここで「正しい」とは、開発者の意図した場所のことを指す。