2.11.2014

C++11: Accumulate with Custom Function, Using Lambda Expression

C++11: 関数オブジェクト付き std::accumulate と ラムダ式の実行例

 

コード

 

 

実行結果

C++11: LCS and LIS

C++11: 最長共通部分列(LCS) と 最長増加部分列(LIS) の実装

C++11 の練習のため、Longest Common Subsequence (LCS) と Longest Increasing Subsequence (LIS) を書いてみる。

 

C++11 の使い方

最近のコンパイラなら、コンパイルオプション「-std=c++0x」を付けるだけで対応できるはず。

Eclipse CDT なら、プロジェクトの Properties を開き
C/C++ Build -> Settings -> Tool Settings -> お使いのC++コンパイラ -> Miscellaneous -> Other flags
に「-std=c++0x」を追加すればよい。

 

C++11 の主な新機能

概要は下記リンク等を参照。

実用性の高そうな機能

  • auto: 型推論
  • constexpr: コンパイル時定数(および定数式)であることの表現
  • range-based for loop: 範囲内を全て走査する for 式
    for ( for-range-declaration : expression )
  • ラムダ式: 以下の構文で無名の関数オブジェクトをお手軽に書ける
    [ lambda-capture ]( parameter-declaration-clause ){ compound-statement }
    

    ちなみに、これは C++11 に限定されないが、いわゆる関数型言語の map, filter, fold(reduce) の機能は
    C++ ではそれぞれ std::transform, std::remove_if, std::accumulate で代替できる。
    (ただし使い勝手は劣る)

  • 初期化リスト構文の拡張: std::vector の初期化も簡単にできるようになった。

    例)

    std::vector<int> v = {1, 2, 3}

 

コード

ideone.com で動かしてみる。

実行結果

Ideone.com - sex6kA - Online C++0x Compiler & Debugging Tool

単純な Dynamic Programming で解いたので、ラムダ式は使いどころが無かった。

2.09.2014

Dictionary Comprehensions in Python

Python: 辞書内包表記

 

Python3 および Python2.7 では集合と辞書の内包表記を利用でき、よりシンプルな表現ができるようになった。

 

逆引き(inverse)辞書を作る
>>> d = {1: 'a', 2: 'b', 3: 'c'}
>>> {v: k for k, v in d.items()}
{'a': 1, 'c': 3, 'b': 2}

dict#iteritems は Python3 では廃止され、dict#items でも同等の性能が出るようになった。

 

リストから辞書を生成
>>> {x: x for x in ['a', 'e']}
{'a': 'a', 'e': 'e'}

 

ちなみに、辞書のキーが全て文字列なら、このような方法で複数の辞書をマージすることも可能。

>>> dict({x: x for x in ['a', 'e']}, **{v: k for k, v in d.items()})
{'a': 1, 'c': 3, 'b': 2, 'e': 'e'}

 

 

References

2.04.2014

Docker: Getting Started with boot2docker

Docker: 母艦 boot2docker に乗り換える

 

こちらのブログポストを読み、

Mac のローカルで docker run するためだけに CentOS を立ち上げるのは贅沢すぎると反省。
そして、母艦を boot2docker に鞍替え。

非常に軽快。
そして今まで使っていたコンテナをそのまま新しい母艦へ運搬する。

この感覚がとても新鮮。

2.02.2014

Docker: Creating Development Environment with SSH

Docker: SSH標準装備の開発環境コンテナイメージの作成

 

Docker コンテナ内で完結する開発環境を作って immutable infra を実現したいというのが動機。
コンテナ内の各種ログを確認したり、ちょっとしたファイルを転送したりするために SSH によるログインも可能としたい。 

 

ファイル構成

 

複数プロセスをまとめる Supervisor

Docker は 1つのアプリケーションを 1つのコンテナで動かすのが基本的な考え方。

複数のプロセスをまとめてデーモンとして動作させるためにはプロセス管理ツールが必要。
その現時点のデファクトスタンダードが Python で書かれたツール、Supervisor である。

 

ファイル内容

作業ディレクトリ配下に Dockerfile と supervisord.conf、そして必要であればミドルウェア等の設定ファイルを置く。

CentOS ベースのコンテナであれば、各ファイルの内容は以下のような構成になる。

  • Dockerfile

    ssh-user という OSユーザを作成し(パスワードも ssh-user)、sudo も可能にする。

    FROM centos:6.4
    
    #
    # SSH
    #
    
    RUN yum install -y --nogpgcheck openssh-server sudo
    RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key && ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key && sed -i 's/UsePAM.*/UsePAM no/g' /etc/ssh/sshd_config
    
    # create user
    RUN useradd ssh-user && echo 'ssh-user:ssh-user' | chpasswd && echo 'ssh-user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
    
    #
    # Supervisor
    #
    
    # enable EPEL repository
    RUN yum install -y --nogpgcheck http://dl.iuscommunity.org/pub/ius/stable/Redhat/6/x86_64/epel-release-6-5.noarch.rpm
    
    # install supervisord
    RUN yum install -y --nogpgcheck supervisor
    
    # settings
    ADD supervisord.conf /etc/supervisord.conf
    RUN mkdir -p /var/log/supervisor
    
    #
    # Other applications here!
    #
    
    EXPOSE 22
    CMD ["/usr/bin/supervisord"]
    
    

    尚、ADD コマンドの後は常に新しいイメージが生成されるようで、キャッシュが使えなくなる。
    Dockerfile を編集して何度もコンテナを作りたい場合は ADD を後回しにした方がいいかもしれない。

  • supervisord.conf
    [supervisord]
    nodaemon=true
    
    [program:sshd]
    command=/usr/sbin/sshd -D
    stdout_logfile=/var/log/supervisor/%(program_name)s.log
    stderr_logfile=/var/log/supervisor/%(program_name)s.log
    autostart=true
    autorestart=true
    

    起動するプロセスを増やしたい場合は、[program:xxx] を増やしてゆけばよい。
    sshd だけは autorestart を有効にした方がいいだろう。

 

Apache + MySQL + PHP 環境のサンプル

例えばお手軽な LAMP環境を使いたい場合は以下のような Dockerfile を作る。

ローカルのみの使用を前提としているので、セキュリティの制限は設けていない。

 

コンテナイメージの作成と起動

 

Docker ホスト SELinux の無効化

まず最初に、Dockerホスト側の SELinux を無効化(/etc/selinux/config -> SELINUX=disabled)する。

これをしないと Dockerコンテナ側で getenforce コマンドがエラーになってしまい、SSH 接続ができなくなる。

(getenforce の中で呼ばれている security_getenforce 関数が負数を返し、結果としてパスワード認証後に通信が切断される。このトラブルシューティングで1日を費やしてしまった)

 

イメージ作成

Docker Registry との連携を考えるなら、イメージの名前にはハイフンではなくアンダースコアを使った方がいい。

$ docker build -t local/apache_mysql_php .

 

コンテナ起動

Dockerホストのポートとの転送設定を -p オプションで指定する。

$ docker run -d -t -p 50000:22 -p 3306:3306 -p 8000:80 local/apache_mysql_php

この例では、SSH(22) を 50000番に、MySQL(3306) はそのまま、Apache(80) は 8000番に割り当てている。

尚、実際の運用では -v /vagrant/app:/app のようにディスクのマウント設定を行い、
アプリケーションの領域を Dockerホストと連携させると良さそうだ。

(さらに Vagrant の synced folder 機能を使って、自分のPCとDockerコンテナのディレクトリを直接リンクさせることもできる)

2014-03-15 追記
環境によっては、docker run に「-t」オプション(pseudo tty 有効化) が必須となるので付けたほうがいい。

 

動作確認

 

Docker

コンテナが起動していることを確認

$ docker ps
CONTAINER ID        IMAGE                           COMMAND                CREATED             STATUS              PORTS                                                                 NAMES
c636e5dbaebf        local/apache_mysql_php:latest   /usr/bin/supervisord   16 seconds ago      Up 14 seconds       0.0.0.0:3306->3306/tcp, 0.0.0.0:50000->22/tcp, 0.0.0.0:8000->80/tcp   angry_pare

 

SSH

ログイン先は Dockerホストの 50000番ポート。

「Dockerホスト」の箇所は環境に合わせて適宜置換(以下同じ)。ログインパスワードは ssh-user。

$ ssh -p 50000 -l ssh-user Dockerホスト
(パスワード入力など)
[ssh-user@70a19c076413 ~]$

 

Apache, PHP

Docker コンテナの中で適当なPHPページを作る。

$ sudo /bin/bash -c "echo '<?php phpinfo(); ?>' > /var/www/html/info.php"

ブラウザで http://Dockerホスト:8000/info.php にアクセスして、phpinfo() の結果が表示されればOK。

 

MySQL

root アカウントで直接操作できるようにしている。

$ mysql -uroot -h Dockerホスト
mysql> select host, user from mysql.user;
mysql> exit

 

これで immutable な開発環境のベースができあがった。

 

 

References

 

2.01.2014

VirtualBox: How to Increase the Virtual Hard Disk Size

VirtualBox: CentOS 仮想ディスクの容量拡張について

 

VirtualBox で立てた Docker ホストの中でコンテナを作り続けていたら、いつしか仮想ディスクの容量が
full になってしまっていた。

VirtualBox のゲストOSで使っているストレージ容量を増やす手順をメモしておく。

 

環境

  • ホストOS: Mac OS X 10.9 (Mavericks)
  • VirtualBox: 4.3.6
  • ゲストOS: CentOS 6.5

こちらの Dockerfile で構築した CentOS (ベースは opscode 提供イメージ) のディスクを
40GB から 80GB へ拡張する。

尚、物理ディスクの空き容量は十分にあるものとする。

 

手順

こちらのブログポストの内容に従って作業。

 

(1) 仮想OS の停止
$ vagrant halt

 

(2) フォーマット変換

ストレージが VMDK 形式の場合、VBoxManage コマンドで直接その容量を変えることはできない。(※1)
いったん VDI 形式のクローンを作ってから、その容量を拡張する。

以下は packer-centos-6.5-x86_64-disk1 というストレージの場合の例。

$ cd ~/VirtualBox\ VMs/ゲストOSの名前
$ VBoxManage clonehd packer-centos-6.5-x86_64-disk1.vmdk packer-centos-6.5-x86_64-disk1.vdi --format VDI
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Clone hard disk created in format 'VDI'. UUID: 32e9d2f1-d30c-4dfc-84dd-24ba96e3be31
$ VBoxManage modifyhd packer-centos-6.5-x86_64-disk1.vdi --resize 81920
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

clonehd コマンドの所要時間は実ディスクの使用量やI/O性能に依存。数十分かかる場合もある。

変換し終わったらVMDK形式のファイルは削除してよいが、バックアップとして暫く残しておくことをお勧めする。

 

(3) ストレージ差し替え

VirtualBox の GUI 上で作業した。

設定画面を開いて、IDE Controller 配下の Primary Master ディスクを VDI 形式のものに付け替え。

Docker host1 Storage 4

 

(4) 仮想OS の起動
$ Vagrantfileのあるディレクトリ
$ vagrant up
$ vagrant ssh

fdisk コマンドで 80GB (+α) が認識されていることを確認。

$ sudo fdisk -l |head -20

ディスク /dev/sda: 85.9 GB, 85899345920 バイト
ヘッド 255, セクタ 63, シリンダ 10443
Units = シリンダ数 of 16065 * 512 = 8225280 バイト
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O size (minimum/optimal): 512 bytes / 512 bytes
ディスク識別子: 0x0004a286

デバイス ブート      始点        終点     ブロック   Id  システム
/dev/sda1   *           1          64      512000   83  Linux
パーティション 1 は、シリンダ境界で終わっていません。
/dev/sda2              64        5222    41430016   8e  Linux LVM

ディスク /dev/mapper/VolGroup-lv_root: 41.4 GB, 41448112128 バイト
ヘッド 255, セクタ 63, シリンダ 5039
Units = シリンダ数 of 16065 * 512 = 8225280 バイト
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O size (minimum/optimal): 512 bytes / 512 bytes
ディスク識別子: 0x00000000

 

(5) パーティションの拡張

fdisk または parted で実行。以下は parted の例。
最初に unit cyl でシリンダ単位で操作するようにしている。ハイライトした行が parted 内での入力。

$ sudo parted
(parted) unit cyl
(parted) p
モデル: ATA VBOX HARDDISK (scsi)
ディスク /dev/sda: 10443cyl
セクタサイズ (論理/物理): 512B/512B
BIOS シリンダ、ヘッド、セクタ geometry: 10443,255,63.  1シリンダは 8225kB。
パーティションテーブル: msdos

番号  開始   終了     サイズ   タイプ   ファイルシステム  フラグ
 1    0cyl   63cyl    63cyl    primary  ext4              boot
 2    63cyl  5221cyl  5157cyl  primary                    lvm

(parted) rm 2
警告: WARNING: the kernel failed to re-read the partition table on /dev/sda (デバイスもしくはリソースがビジー状態です).  As a result, it may not reflect all of your changes until after reboot.
(parted) p
モデル: ATA VBOX HARDDISK (scsi)
ディスク /dev/sda: 10443cyl
セクタサイズ (論理/物理): 512B/512B
BIOS シリンダ、ヘッド、セクタ geometry: 10443,255,63.  1シリンダは 8225kB。
パーティションテーブル: msdos

番号  開始  終了   サイズ  タイプ   ファイルシステム  フラグ
 1    0cyl  63cyl  63cyl   primary  ext4              boot

(parted) mkpart primary 63 10443
警告: 63cyl から 10443cyl までのパーティションを指定されました。
可能な中で最も近いものは 63cyl から 10443cyl になります。
それでもかまいませんか?
はい(Y)/Yes/いいえ(N)/No? y
警告: WARNING: the kernel failed to re-read the partition table on /dev/sda (デバイスもしくはリソースがビジー状態です).  As a result, it may not reflect all of your changes until after reboot.
(parted) p
モデル: ATA VBOX HARDDISK (scsi)
ディスク /dev/sda: 10443cyl
セクタサイズ (論理/物理): 512B/512B
BIOS シリンダ、ヘッド、セクタ geometry: 10443,255,63.  1シリンダは 8225kB。
パーティションテーブル: msdos

番号  開始   終了      サイズ    タイプ   ファイルシステム  フラグ
 1    0cyl   63cyl     63cyl     primary  ext4              boot
 2    63cyl  10443cyl  10379cyl  primary

(parted) set 2 lvm on
警告: WARNING: the kernel failed to re-read the partition table on /dev/sda (デバイスもしくはリソースがビジー状態です).  As a result, it may not reflect all of your changes until after reboot.
(parted) p
モデル: ATA VBOX HARDDISK (scsi)
ディスク /dev/sda: 10443cyl
セクタサイズ (論理/物理): 512B/512B
BIOS シリンダ、ヘッド、セクタ geometry: 10443,255,63.  1シリンダは 8225kB。
パーティションテーブル: msdos

番号  開始   終了      サイズ    タイプ   ファイルシステム  フラグ
 1    0cyl   63cyl     63cyl     primary  ext4              boot
 2    63cyl  10443cyl  10379cyl  primary                    lvm

(parted) q
通知: 必要であれば /etc/fstab を更新するのを忘れないようにしてください。

設定が終わったら、ssh から抜けてリブート(vagrant halt; vagrant up)する。

 

(6) LVM 物理ボリューム (physical volume) サイズの拡張

pvscan で確認、pvresize で変更。

$ sudo pvscan
  PV /dev/sda2   VG VolGroup   lvm2 [39.51 GiB / 0    free]
  Total: 1 [39.51 GiB] / in use: 1 [39.51 GiB] / in no VG: 0 [0   ]
$ sudo pvresize /dev/sda2
  Physical volume "/dev/sda2" changed
  1 physical volume(s) resized / 0 physical volume(s) not resized
$ sudo pvscan
  PV /dev/sda2   VG VolGroup   lvm2 [79.51 GiB / 40.00 GiB free]
  Total: 1 [79.51 GiB] / in use: 1 [79.51 GiB] / in no VG: 0 [0   ]

vgdisplay コマンドで状態を確認。

$ sudo vgdisplay
  --- Volume group ---
  VG Name               VolGroup
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  4
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               79.51 GiB
  PE Size               4.00 MiB
  Total PE              20354
  Alloc PE / Size       10114 / 39.51 GiB
  Free  PE / Size       10240 / 40.00 GiB
  VG UUID               Iv3nnF-GabP-gmv5-sU9W-iGtb-y5yg-9MA68g

Total PE 20354 に対して、Alloc PE が 10114 であることがわかった。

 

(7) LVM 論理ボリューム (logical volume) サイズの拡張

lvdisplay で確認、lvextend で変更。
空き領域を全て使用する場合には -l +100%FREE オプションが便利だ。

$ sudo lvdisplay
  --- Logical volume ---
  LV Path                /dev/VolGroup/lv_root
(略)
  LV Size                38.60 GiB
(略)
$ sudo lvextend -l +100%FREE /dev/VolGroup/lv_root
  Extending logical volume lv_root to 78.60 GiB
  Logical volume lv_root successfully resized
$ sudo lvdisplay
  --- Logical volume ---
  LV Path                /dev/VolGroup/lv_root
(略)
  LV Size                78.60 GiB
(略)

 

(8) ファイルシステムのサイズ拡張

最後に、resize2fs で対象のファイルシステムのサイズを変更。

$ sudo resize2fs /dev/VolGroup/lv_root
resize2fs 1.41.12 (17-May-2010)
Filesystem at /dev/VolGroup/lv_root is mounted on /; on-line resizing required
old desc_blocks = 3, new_desc_blocks = 5
Performing an on-line resize of /dev/VolGroup/lv_root to 20604928 (4k) blocks.
The filesystem on /dev/VolGroup/lv_root is now 20604928 blocks long.

$ df -h
Filesystem                    Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root   78G   38G   36G  52% /

無事、ファイルシステム上で使えるディスク容量を 40GB 増やすことに成功した。

 

 

 

※1 現バージョンでは対応していないフォーマットだと表示される。

$ VBoxManage modifyhd packer-centos-6.5-x86_64-disk1.vmdk --resize 81920
0%...
Progress state: VBOX_E_NOT_SUPPORTED
VBoxManage: error: Resize hard disk operation for this format is not implemented yet!

1.11.2014

Scala: Parallel SBT Test with Docker Containers

Scala: Docker を使って sbt test を並列化してみた

 

Docker + CI が巷で流行しているそうなので便乗。

 

動機

ここに、残念なテストがある。

ローカル上の Redis サーバに接続し、100件のレコードを投入した後で 1件削除。
そのときにレコード数が 99件かどうかを検証するだけの簡単なプログラムだ。

これが全部で 4セットほどある。

このテストは明らかに

  • 無駄な sleep が入っていて実行時間が長い (1クラスごとに10秒待たされる)
  • テストを並列で実行するとカウントがずれてテストが通らない場合がある
  • テストを流す前に既に DB に他のデータが入っていたら、カウントがずれてテストが通らない

というような問題を抱えている。

 

単に実行すると 48秒かかる
$ sbt test
(略)
[info] Passed: Total 4, Failed 0, Errors 0, Passed 4
[success] Total time: 48 s, completed Jan 11, 2014 1:35:59 AM

 

parallelExecution を有効にするとテストが通らない
$ sbt 'set parallelExecution in Test := true' test
(略)
[info] test1 should
[error] x insert 100 records and delete 1 record
[error]    'Some(96)' is not Some with value '99' (RedisConnectionSpec.scala:27)
(略)
[error] Failed: Total 4, Failed 4, Errors 0, Passed 0
[error] Failed tests:
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec1
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec2
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec3
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec4
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 14 s, completed Jan 11, 2014 1:37:52 AM

 

テストデータ以外が含まれていると、やはりテストが通らない
$ redis-cli set 'foo' 'bar'
OK
$ sbt test
(略)
[info] test1 should
[error] x insert 100 records and delete 1 record
[error]    'Some(100)' is not Some with value '99' (RedisConnectionSpec.scala:27)
(略)
[error] Failed: Total 4, Failed 4, Errors 0, Passed 0
[error] Failed tests:
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec4
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec3
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec2
[error]         com.github.mogproject.dockersbttest.example.RedisConnectionSpec1
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 46 s, completed Jan 11, 2014 1:39:52 AM
$ redis-cli del 'foo'
(integer) 1

 

アイデア

当然ながら、不要な待ち時間を減らしたり、モックを作ったりして状態への依存を極力少なくした
正しいテストを書くのが最もよいアプローチだと思う。

しかし現実にはそれが難しかったり、対応に時間がかかってしまう場合もある。

そこで、Docker を使ってテストクラスの単位で独立したコンテナを立ち上げ
その中で個別のテスト(sbt 'test-only クラス名')を実行したいと考えた。

処理の流れは以下のとおり。

  1. [事前準備] あらかじめ、テストで使う Docker イメージを作成しておく。(以下、ベースイメージとする)
  2. [前処理] ベースイメージに対して、2種類の変更を行い新規イメージを作成。
    そのイメージ(以下、テストイメージとする) に対して新しいタグを付ける。
    • 事前コマンド
      テストの度に実行するコマンドがあれば、それを登録。
      モジュールの最新化 (git pull) などを想定。
    • コンパイル
      テストを流すたびにコンパイルが走ると遅くなるので、事前に sbt compile test:compile を実行
  3. [テスト一覧取得] テストイメージを使って、テスト対象クラス名の一覧を取得する。
    これは sbt 'show test:defined-test-names' コマンドで確認できた。
     
  4. [テスト並列実行] 手順 3 で取得したクラス名に対して、並列で以下の処理を実行。
    • テストイメージを使って新規コンテナを起動
    • コンテナの中で sbt 'test-only クラス名' を実行 (実行中の標準出力はバッファリング)
    • テストが完了したら、コンテナは停止する
    • コマンドのリターンコードでテスト成功/失敗を判断
    • バッファリングしていた標準出力を全て出力
    • コンテナを削除 

    並列処理の多重度は、Docker ホストマシンの CPU数と同じにする。

  5. [結果表示] 全てのテストが完了したら、結果一覧を表示。
  6. [後処理] テストイメージを削除。

 

動作イメージ

Docker sbt test

 

作ってみた

上記の 2. 〜 6. の処理を行うスクリプトを Python で書いてみた。

docker_sbt_test.py というスクリプトに対して、ベースイメージのリポジトリ名(+タグ名)、テストイメージ内部にある sbt プロジェクトのディレクトリ、ベースイメージに対して行う事前実行コマンドを指定して実行する。

docker_sbt_test.py REPOSITORY[:TAG] [options]

Options:
  -h, --help            show this help message and exit
  -d DIR, --dir=DIR     path to the sbt project directory in the container
  -s SETUP, --setup=SETUP
                        commands to be executed in the shell before testing

より詳しい実行例はこちらを参照。

 

実行結果

手元 (4CPU, 4GB MEM の VirtualBox環境) で実行してみた結果はこちら。

それぞれのテストが並列で実行され、全て成功。sbt test-only は14〜15秒ほどで完了した。

ログ中の starting sbt と finished sbt の間で 30秒ほどかかっているように見えるが、
これは docker コンテナの起動/終了処理と sbt 自体の起動処理にかかる時間が含まれるためと考えられる。
(ちなみに、単体実行時でも sbt の起動には 5秒程度かかっていた。)

VirtualBox 環境でなければもっと速いかもしれない。

  • 抜粋
    $ ./docker_sbt_test.py local/sbt-test --dir /workspace/docker-sbt-test/example --setup " \
    rm -fr /workspace/docker-sbt-test && \
    git clone --depth 1 https://github.com/mogproject/docker-sbt-test.git /workspace/docker-sbt-test \
    "
    2014-01-11 07:24:53,272 [INFO] Creating test image (local/sbt-test:test-1389425093.27) ...
    (略)
    2014-01-11 07:25:13,073 [INFO] Compiling...
    (略)
    2014-01-11 07:25:41,707 [INFO] Getting test class names...
    2014-01-11 07:25:48,859 [INFO] Starting sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec1' ...
    2014-01-11 07:25:48,859 [INFO] Starting sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec2' ...
    2014-01-11 07:25:48,859 [INFO] Starting sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec3' ...
    2014-01-11 07:25:48,859 [INFO] Starting sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec4' ...
    2014-01-11 07:26:13,257 [INFO] Finished sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec3' ...
    Starting redis-server: [  OK  ]
    (略)
    [info] RedisConnectionSpec3
    [info]
    [info] test3 should
    [info] + insert 100 records and delete 1 record
    [info]
    [info]
    [info] Total for specification RedisConnectionSpec3
    [info] Finished in 39 ms
    [info] 1 example, 0 failure, 0 error
    [info]
    [info] Passed: Total 1, Failed 0, Errors 0, Passed 1
    [success] Total time: 15 s, completed Jan 11, 2014 2:26:13 AM
    
    2014-01-11 07:26:13,600 [INFO] Finished sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec4' ...
    (略)
    [info] Passed: Total 1, Failed 0, Errors 0, Passed 1
    [success] Total time: 14 s, completed Jan 11, 2014 2:26:13 AM
    
    2014-01-11 07:26:13,911 [INFO] Finished sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec2' ...
    (略)
    [info] Passed: Total 1, Failed 0, Errors 0, Passed 1
    [success] Total time: 14 s, completed Jan 11, 2014 2:26:13 AM
    
    2014-01-11 07:26:14,035 [INFO] Finished sbt 'test-only com.github.mogproject.dockersbttest.example.RedisConnectionSpec1' ...
    (略)
    [info] Passed: Total 1, Failed 0, Errors 0, Passed 1
    [success] Total time: 14 s, completed Jan 11, 2014 2:26:13 AM
    
    2014-01-11 07:26:14,080 [INFO] *** SUMMARY ***
    2014-01-11 07:26:14,081 [INFO] com.github.mogproject.dockersbttest.example.RedisConnectionSpec1 -> OK
    2014-01-11 07:26:14,081 [INFO] com.github.mogproject.dockersbttest.example.RedisConnectionSpec2 -> OK
    2014-01-11 07:26:14,081 [INFO] com.github.mogproject.dockersbttest.example.RedisConnectionSpec3 -> OK
    2014-01-11 07:26:14,081 [INFO] com.github.mogproject.dockersbttest.example.RedisConnectionSpec4 -> OK
    2014-01-11 07:26:14,081 [INFO] ***************
    2014-01-11 07:26:14,081 [INFO] Removing test image (local/sbt-test:test-1389425093.27) ...
    (略)
    
  • 全文 -> docker-sbt-test-example-result.txt

 

注意事項

こちらに dstat の結果を載せたが、複数のコンテナを同時に起動すると
思ったよりメモリリソースの消費が激しいようだ。

スワップが起こるような状況であれば、もちろん並列化しない時よりもずっと遅くなる。

 

まとめ

  • sbt test を並列化する最善のアプローチは parallelExecution を有効にしても通るテストを書くこと。

    ただ、同時に実行できない多数のテストが存在する場合でも、Docker + 並列化スクリプトで
    高速化できる可能性がある。
     
  • テストが外部環境の状態に依存する場合も、Docker を使えばクリーンな環境を保証できる。

 

 

 

References