2013年5月11日土曜日

GAE/PythonアプリのテストをTravis CIで行う



幾つかあるCIサービスのうち、Travis CIを使ってみようと思い試してみた。

前提

  • 手元にGoogle App Engineを利用したPythonプロジェクトがあること
  • そのプロジェクトをGitHubのPublicプロジェクトとして管理していること
  • Pythonのunittestを行える環境がローカルに存在すること
  • Travis CIのアカウントを持っていること

GAE SDKを利用したスクリプトの作成

これをそのまま適当な名前でプロジェクト直下に保存すればよい。
ここではtest_gae.pyとする。

テストの作成

tests/を作成し、その直下に__init__.py(空でよい)とテストスクリプトを書く。次のものは、nameとownerプロパティを持つGroupモデルのテスト例。



テストの実行

python test_gae.py <Google App Engine SDKのパス> <テストディレクトリ>
(e.g. python test_gae.py /usr/local/google_appengine tests/)

実行結果

これでテストは通った。

CI対象プロジェクトの指定

ログイン → Accountsで表示されるプロジェクトから、
Travis CIの対象にするプロジェクトのスイッチをONにする。

CIプロジェクトの設定

Travis CI用設定ファイルの作成

Google App Engine SDKをダウンロードしてパスに通す荒業を実行する。
というわけで.travis.ymlを次のように書いてプロジェクト直下に保存する。


install → 必要なパッケージの指定(requirements.txt: pip freezeの内容)
before_script → Google App Engine SDKのダウンロードおよび展開
script → テストの実行

これで、次回以降のGitHubへのプッシュ時にTravis CIでscriptのテスト内容が実行される。

[追記]

travis-lintというgemが存在する。
gem installしてtravis-lintコマンドをプロジェクト直下で実行すると、
.travis.ymlが正しいか否かを確認できる。



2013年5月4日土曜日

メタプログラミングRuby勉強会#1



ちょうど仕事と関連しているのでタイムリーな勉強会。
単に手を動かしたせいかもしれないが、充実した時間を過ごせた。

昨日のメインは第一章だったが、オープンクラス等Rubyっぽさを感じられた。


オブジェクトとクラス

オブジェクト自身がインスタンスメソッドを持つ訳ではない。オブジェクトはクラスのメソッドへの参照を持っているだけ。superclassにはモジュールが見えないがancestorsでは見える。

メソッド呼び出し

メソッドが呼び出されると現在のオブジェクトのクラス内でそのメソッドを探す。なければ親クラスを見る。ancestorsを辿れば分かりやすい。

privateメソッド

Rubyでは親クラスのprivateメソッドを呼び出せる。Javaのprotectedみたいなものか。


次回は動的ディスパッチャやmethod_missing等Rubyの沼に潜っていく。




2013年4月28日日曜日

もくもくプログラミングの会#3


久しぶりにアルゴリズムの勉強を少々。
普段の業務とまったく異なることを行うことは刺激があるものである。
(結局は普段の業務に何らかの還元があると考えているが)


今日は久しぶりにこの本を開いて1つの配列で3つのスタックを実現するコードを書いた。
スタックサイズが固定なのはご勘弁いただきたい。久々のPythonだったせいか、
クラスの書き方すら忘れてたのには我ながら驚いた。


これまでSyntaxHilighterを使ってたけど、Gistを使う方が
ブログ埋め込みが楽なのに気づいたのも今日の収穫の一つ。

Asset Pipelineについて復習


概要 (ここを見ながら)

  • JavaScriptおよびCSSに対し、結合 and (最小化 or 圧縮)を行うフレームワーク
  • CoffeeScript / Sass / ERBのような他言語で書いても処理してくれる


Rails 3.1より前はJammitおよびSprocketsのような3rdパーティライブラリを使い
実現していた。3.1ではSprocketsとデフォルトで (ActionPackが) 統合されている。

デフォルトではアセットパイプラインが有効になっている。
無効にするには config/application.rb で次のように設定する。
# Enable the asset pipeline
config.assets.enabled = false

圧縮やダイジェストの設定は config/environments/xxxx.rb で設定されてる。
config.assets.compress = true (production.rb)
config.assets.digest = true  (production.rb)

上記を読んで分かったつもりになってはいけない、ということで動作確認


テストアプリケーションを用意

$ rails new assetpipelinetest
$ rails g scaffold book title:string author:string
$ rm public/index.html
$ config/routes.rb
     root :to => 'books#index'
$ rake db:migrate 


ファイルの読み込みを確認 (Development)

次の3ファイルを作成
  • app/assets/javascripts/a.js
  • lib/assets/javascripts/b.js
  • vendor/assets/javascripts/c.js  


起動後にトップページへアクセスし確認、ソースを見るとa.jsだけが読み込まれている。
<script src="/assets/a.js?body=1" type="text/javascript"></script>
しかし、b.jsとc.jsは読み込まれていない。a.jsが読み込まれているのは
application.js (マニフェストファイル) に次の記述があるから。
//= require_tree .

これにより、appliation.jsと同じかサブディレクトリに存在する
JavaScriptやCofeeScriptを自動で読みこむ。

続けてbとcを読み込むようにマニフェストファイル編集
//= require b
//= require c

<script src="/assets/b.js?body=1" type="text/javascript"></script>
<script src="/assets/c.js?body=1" type="text/javascript"></script>

これで3つのjsファイルが読み込まれるようになった。
cssについても、マニフェストファイル (application.css) に書けば読み込まれる。


プリコンパイル (Production)

まずはテーブルを用意
rake db:migrate RAILS_ENV=production

単にWEBrickを起動しただけでは次のエラーが出る。
ActionView::Template::Error (application.css isn't precompiled):

そのため、プリコンパイルせねばならぬ。
rake assets:precompile

これで public/assets にアセットファイルが用意される。
application-00e4a643d1be5b0c4b39be21673d21b2.css
application-4f4e444aeb28497bca7d81506213536e.js

同コマンドで作られるmanifest.ymlには
アセットファイルと実際のファイル名が記述されている。
application.js: application-4f4e444aeb28497bca7d81506213536e.js
application.css: application-00e4a643d1be5b0c4b39be21673d21b2.css

上記はダイジェストが有効な状態での実行結果。
試しに無効化して再度コンパイルすると、manifest.ymlの中は次のようになる。
application.js: application.js
application.css: application.css 

config.assets.compressをfalseにした場合は
application.jsやapplication.cssの中身が圧縮されない。
また、他の特徴として、コンプレッサの指定指定も可能。

とりあえずこんなところか。

2013年4月27日土曜日

[書評] Trac入門

 真面目な感想とか。

記憶に無いほど昔、前職の同僚が草稿を書いていたので少し見させてもらった。気づいたことや感想を伝えたら最近出版された本を貰った。せっかく頂いた(献本というらしい)のでちゃんと読後の感想を書かねばと思いつつ1つ困ったことがあった。残念ながらTracをちゃんと使った記憶が無いのである。5年以上前に遊びで自宅サーバに入れて触ったことはあった気がするという程度。しかし、Tracはレイアウトというか色合いというか見た目が割と好きな方なので頑張って書いてみたいと思う。

さて、巷にはプロジェクト管理、チケット(課題)管理、バグ管理などを目的とするシステムがあるし、ほとんどの開発者がそうしたものに触っていると思う。しかし、良いツールを使っていてもプロジェクトがうまく回るとは限らない。実際うまくいっていないプロジェクトの方が多いだろう。そうしたなか、こうした入門書で何のためにツールを使っているのかを見直すことには意味があるかもしれない。


Tracを何のために使うか?



  • 基本的なプロジェクト情報の共有
  • マイルストーンの共有
  • 実績の把握
  • テスト状況の把握
  • 課題(チケット)の共有・管理


例としていくつか挙げたが、プロジェクトの状況は常に変化するので常に情報と状況を把握しておくことが重要である。Tracはあくまでその為のツール。


勝手に対象読者を設定



  • 先に挙げた情報をうまく把握できていないと自覚してる人
  • 紙ベースで管理しているがやり方を変えたいと考えている人
  • Excelで管理しているが (ry
  • 迷走している人



本書で得られるもの


話を分かりやすくしようとする為にマンガのストーリもあるので、
こうしたツールに慣れていない人にも利用シーンを想像しやすくなっている。

特に印象に残ったのは、チケット(課題)のフローについての丁寧な説明が
為されていることである。チケットにはデフォルトで5つのステータスがある。


  • new / assigned / accepted / reopened / closed
  • 新規 / アサイン済み / 受け入れ済み / 再開 / クローズ


これらのステータスが、図を使いながら分かりやすく記述されている。
異なるステータスを必要とする場合には新しいステータスを作り、
読者のプロジェクトに合ったフローを作ることができる。
それも深いカスタマイズを必要とせず、設定ベースで簡単に行える。

次に印象に残ったのはGit、CI連携が紹介されている章である。
最近の開発プロジェクトでは分散型ソースコード管理システムを使うことが
多いと思うが、そうした声に応えて加筆されたものと考えられる。


  • Gitの基本的な使い方: リポジトリの作成やブランチの概念
  • GitとTracの連携: 具体的な設定方法
  • TracとJenkinsの連携: JenkinsからTracチケットへのリンク自動作成


内容ではGitやJenkinsの専門書に及ばないものの、
これらのツールの導入を躊躇していた場合には突破口となりうる。

他にも、ガントチャートを使った見える化や
バーンダウンチャートの表示などについての逆引きもあり、
これからTracの導入を考えるプロジェクトには本書をお薦めできる。


その他


Git、Jenkinsとの連携およびベストプラクティスの部分を
もっと厚くした続編があると面白いかもしれないと感じた。

どんな本でも謝辞を目にするとこっちが恥ずかしくなるのは何でだろう。

2013年4月5日金曜日

Railsアプリがアボートした話 + α


よくあるApache2 + Phusion Passengerの構成で
Railsアプリケーションを動かそうとしたらクラッシュしたので
調査して対応したという話。


# Internal Server Error 500

アクセスしてアプリを触ってたらみんな大好き500エラーが出てた。
なのでApacheのエラーログを見ることに。


# とりあえずログ (抜粋)

[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html
[ pid=2662 thr=139689980598240 file=ext/apache2/Hooks.cpp:841 time=2013-04-02 14:45:09.779 ]: The backend application (process 6194) did not send a valid HTTP response; instead, it sent nothing at all. It is possible that it has crashed; please check whether there are crashing bugs in this application.
/usr/local/rvm/gems/ruby-1.9.3-p362/gems/activerecord-3.2.11/lib/active_record/relation.rb:241: [BUG] Segmentation fault
ruby 1.9.3p362 (2012-12-25 revision 38607) [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0101 p:0016 s:0578 b:0575 l:000574 d:000574 METHOD /usr/local/rvm/gems/ruby-1.9.3-p362/gems/activerecord-3.2.11/lib/active_record/relation.rb:241
c:0100 p:0026 s:0572 b:0572 l:000571 d:000571 METHOD /usr/local/rvm/gems/ruby-1.9.3-p362/gems/activerecord-3.2.11/lib/active_record/scoping/default.rb:41
c:0099 p:0022 s:0569 b:0569 l:001278 d:000568 LAMBDA /usr/local/rvm/gems/ruby-1.9.3-p362/gems/activerecord-3.2.11/lib/active_record/scoping/named.rb:180
(中略)
7fa5207ac000-7fa5207ad000 r--p 0001f000 ca:01 4240                       /lib64/ld-2.12.so
7fa5207ad000-7fa5207ae000 rw-p 00020000 ca:01 4240                       /lib64/ld-2.12.so
7fa5207ae000-7fa5207af000 rw-p 00000000 00:00 0
7fff2e37c000-7fff2e39d000 rw-p 00000000 00:00 0                          [stack]
7fff2e3ff000-7fff2e400000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
(中略)
[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html
[ pid=2665 thr=139689980598240 file=ext/apache2/Hooks.cpp:841 time=2013-04-02 14:52:29.683 ]: The backend application (process 6329) did not send a valid HTTP response; instead, it sent nothing at all. It is possible that it has crashed; please check whether there are crashing bugs in this application.

did not send a valid HTTP responseは多分問題無いということが
他のソースから分かっていたのでこの時点でRuby周りかなという感じだった。
ひとまず、すぐに確認できそうなことから可能性を潰すことにした。


# 再現および状況把握

それ以降も何度も発生したのでサーバリソース周りを見ておくことにした。
sysstat入れて久しぶりにsarコマンド使ってCPU/Memory/IOの様子を確認。

=>異常無し


# 可能性の検討 + 行動

  1. Rubyインタプリタのバグ踏んだ (ログの通り。人間素直が一番)
  2. Passenger, Rails等いずれかのライブラリのバグ (ログからは一度Passengerの可能性も考えた)
  3. ApacheやPassengerがsarで汲み取れない一瞬で何らかの閾値を振り切った (スカウターで確認できない程の一瞬で戦闘力を上げて攻撃されたイメージ)
可能性として1, 2, 3の順に高いと考え、
StackOverflowやら何やらで情報を探すと以下のものに突き当たった。

  • https://github.com/gitlabhq/gitlabhq/issues/2436
  • https://github.com/gitlabhq/gitlabhq/issues/2476

結論としては偶々サーバに入ってたバージョンのRubyが
buggyだったのでアップデートしろということだった。
これ以外にもStackOverflowでログがほぼ同じ投稿があったけど
解決方法がログやテンポラリスペースが云々とあったので無関係と判断。

その後、
Rubyのバージョンを1.9.3p362からp392に上げて、
Apacheのconfigを書き換えた。

$ sudo rvm get stable
$ rvm reload
$ rvm list known
$ sudo rvm autolibs enable
$ sudo rvm upgrade 1.9.3-p362
$ rvmsudo passenger-install-apache2-module

LoadModule passenger_module /usr/local/rvm/gems/ruby-1.9.3-p392/gems/passenger-3.0.19/ext/apache2/mod_passenger.so
PassengerRoot /usr/local/rvm/gems/ruby-1.9.3-p392/gems/passenger-3.0.19
PassengerRuby /usr/local/rvm/wrappers/ruby-1.9.3-p392/ruby

=>クラッシュしなくなった。


# 自分への教訓

メインで使ってるソフトウェアのバグやセキュリティフィックスくらいは
恒常的に意識的に追いかけましょう。


# + α

4/1から新しい会社で働いています。

2013年3月10日日曜日

二分探索木の基本

今日は、もくもくとプログラミングする会でツリー及び変更の実装を行った。
ここまで書き忘れていたが、最近のアルゴリズム勉強はこの本(↓)でやってる。

 

非常に良い書籍。
普段の業務ではこのような学習(復習)をする機会が少ないので
意図的に時間を費やしている。(C++に慣れることも副次的狙い)

ツリーのノードを表現する構造体
typedef struct tag_tree_node {
    int data;
    struct tag_tree_node *left;
    struct tag_tree_node *right;
} tree_node;


ノードの作成
tree_node* create_node(int data) {
    tree_node *new_node = (tree_node*)malloc(sizeof(tree_node));
    if (new_node == NULL) {
        exit(EXIT_FAILURE);
    }
    new_node->left = NULL;
    new_node->right = NULL;
    new_node->data = data;
    return new_node;
}


ノードの挿入
void insert_node(int data, tree_node *node) {
    if (node == NULL){
        root_node = create_node(data);
        return;
    }
    if (node->data > data){
        if (node->left == NULL)
            node->left = create_node(data);
        else
            insert_node(data, node->left);
    } else {
        if (node->right == NULL)
            node->right = create_node(data);
        else
            insert_node(data, node->right);
    }
    return;
}


ノードの検索
tree_node* find_node(int data, tree_node *node) {
    if (node->data > data) {
        if (node->left == NULL)
            return NULL;
        return find_node(data, node->left);
    }
    if (node->data < data) {
        if (node->right == NULL)
            return NULL;
        return find_node(data, node->right);
    }
    return node;
}


ノードの削除
int delete_node(int data) {
    tree_node *node = root_node;
    tree_node *parent = NULL;
    tree_node *left_biggest;
    int dir = 0;

    while (node != NULL && node->data != data) {
        if (node->data > data) {
            parent = node;
            node = node->left;
            dir = -1;
        } else {
            parent = node;
            node = node->right;
            dir = 1;
        }
    }
    if (node == NULL)
        return 0;
    
    if (node->left == NULL || node->right == NULL) {
        if (node->left == NULL) {
            switch (dir) {
            case -1:
                parent->left = node->right;
                break;
            case 1:
                parent->right = node->right;
                break;
            case 0:
                root_node = node->right;
            }
        } else {
            switch (dir) {
            case -1:
                parent->left = node->left;
                break;
            case 1:
                parent->right = node->right;
                break;
            case 0:
                root_node = node->left;
            }
        }
        free(node);
    } else {
        left_biggest = node->left;
        parent = node;
        dir = -1;
        while (left_biggest->right != NULL) {
            parent = left_biggest;
            left_biggest = left_biggest->right;
            dir = 1;
        }
        node->data = left_biggest->data;
        if (dir == -1)
            parent->left = left_biggest->left;
        else
            parent->right = left_biggest->left;
        free(left_biggest);
    }
    return 1;
}


ノードの解放
void free_node(tree_node *node) {
    if (node == NULL)
        return;
    free_node(node->left);
    free_node(node->right);
    free(node);
}


ツリーの表示(雑だけど...)
void print_tree(int depth, tree_node *node, int dir) {
    int i;
    for (i = 0; i < depth; i++)
        cout << "  ";
    switch (dir) {
    case -1:
        cout << "L: ";
        break;
    case 1:
        cout << "R: ";
        break;
    }
    cout << node->data << endl;
    if (node->left != NULL)
        print_tree(depth + 1, node->left, -1);
    if (node->right != NULL)
        print_tree(depth + 1, node->right, 1);
}


メイン実装
int main() {
    srand((unsigned int)time(NULL));
    int i, ar[N];
    for (i = 0; i < N; i++) {
        ar[i] = rand() % 100 + 1;
        insert_node(ar[i], root_node);
    }
    // original
    print_tree(0, root_node, 0);

    // insert
    int insert = rand() % 100 + 1;
    cout << "---------- insert: " << insert << endl;
    insert_node(insert, root_node);
    print_tree(0, root_node, 0);

    // delete
    int del = ar[rand() % N];
    cout << "---------- delete: " << del << endl;
    delete_node(del);
    print_tree(0, root_node, 0);

    return EXIT_SUCCESS;
}




[追記]
前述のもくもくとプログラミングする会は最近大学院の同級生と始めたところ。
現時点では不定期なプライベートイベントだけど参加したい人がいれば連絡下さい。