Sunday, June 29, 2014

Frontend Testing on Javascript

ここ最近のWeb APIフロントエンド部のテスト方法について調べてみる.

Test Runner


Jasmine
RSpecよりのテスター.

QUnit.
XUnit, JUnitとかCPPUnitの系列.

Mocha
node.js, ブラウザで動作するJavascript用テストフレームワーク. 昨日のYeomanは、Scaffolding時にこれをインストールしていた.

Cucumber
自然言語というと言い過ぎだけどテストコードをテスト手順っぽく書ける便利なテスタ.
Cucumber lets software development teams describe how software should behave in plain text. The text is written in a business-readable domain-specific language and serves as documentation, automated tests and development-aid - all rolled into one format.

Cucumber works with Ruby, Java, .NET, Flex or web applications written in any language. It has been translated to over 40 spoken languages.
---
Cucumberは普通のテキストでソフトウェアを振る舞いを記述できます. テキストはビジネスよりのドメイン記述言語として記述されています. このフォーマットだけで開発文書、自動テスト、development aidを提供します.

CucumberRuby, Java, Net, Flex, などあらゆる言語で書かれたWebアプリケーションに適用可能です. 40以上の言語に翻訳されています.

アサーションライブラリ

Chai
他のテストフレームワークと組み合わせて使うBDD/TDDアサーションライブラリ. MochaとChaiだからデフォルトの組み合わせを意識しているんだろうな…

TDD/BDD

JavascriptだとBDDを前提にするテストフレームワークが多い印象.
上に挙げたものを分類してみるとJasmine, Mocha,CucumberはBDD,QUnitがTDDを指向しており、使用率はJasmineとMochaで5割超えているとか.
TDD : Test Driven Development
BDD : Behavior Driven Development
(なんか最近略称が増えてきてこんがらがってくるorz)

ドライバとかスタブみたいなやつ

Sinon.js

ブラウザのエミュレータ?

Phantom.js

Phantom.js](http://phantomjs.org/ “Phantom.js”)
ブラウザ無しでテストを実行するエミュレータさん. これ自身はテスト機能を持っておらず上で挙げたJasmineとQUnitと組み合わせて使うのかな.
公式ページには「PhantomJS is a headless WebKit scriptable with a JavaScript API」と載っている.

Capybara

Capybara.
Rails用のテストフレームワークみたいだけど別にRailsアプリケーションでなくても使えるみたいだから挙げてみる.
公式サイトから 転載
Capybara is a library written in the Ruby programming language which makes it easy to simulate how a user interacts with your application.
----
CapybaraRubyで書かれたライブラリです. アプリケーションに対するユーザのインタラクティブな操作をシミュレートします.

試用開始

いっぱいあるなぁ…いいと思う.やめたくなったけど、せっかくだから試していく.

Capybara x Cucumber

準備

どちらもgemでインストールできる. 下記サイトを見ながらやってみた. あっても別に困らないからrspecもインストール. 昨日RedmineがらみでRailsも入ってるし.
http://www.atmarkit.co.jp/ait/articles/1302/20/news032.html
yukaary$ gem install capybara capybara-webkit headless cucumber rspec --no-ri --no-rdoc
...
13 gems installed
全部入ったと思ったらcapybara-webkitが入ってない. 調べてところqt無いと駄目と言われたのでbrew経由で入れる.
yukaary$ brew update
error: Your local changes to the following files would be overwritten by merge:
    Library/Formula/readline.rb
Please, commit your changes or stash them before you can merge.
Aborting
Error: Failure while executing: git pull -q origin refs/heads/master:refs/remotes/origin/master
はい失敗. 更に調べてbrewは/usr/localをgitリポジトリにしてるらしいからこれを手動でリモートに同期させろとかなんとか.
yukaary$ cd $(brew --prefix)
yukaary$ git fetch origin
yukaary$ git reset --hard origin/master
yukaary$ brew update
Already up-to-date.
なんとかなってそう.
yukaary$ brew install qt
Building native extensions.  This could take a while...
Successfully installed capybara-webkit-1.1.1
Parsing documentation for capybara-webkit-1.1.1
Installing ri documentation for capybara-webkit-1.1.1
Done installing documentation for capybara-webkit after 2 seconds
1 gem installed
やったぜ.
入ったライブラリのバージョン.
  • capybara 2.3.0, 2.2.0.rc1
  • capybara-webkit 1.1.1
  • headless 1.0.2
  • cucumber 1.3.15
  • rspec 3.0.0
ここでcapybaraが2.3.0と2.2.0.rc1の2バージョン入っているのはcapybara-webkit 1.1.1が2.2.0.rc1に依存しているからみたい.この後にcapybaraを動かしたときに「capybaraが競合しているぞ」とか言われて失敗したので2.3.0のほうはアンインストールした.
yukaary$ gem uninstall capybara

Select gem to uninstall:
 1. capybara-2.2.0.rc1
 2. capybara-2.3.0
 3. All versions
> 2
Successfully uninstalled capybara-2.3.0
更にheadlessというgemがバックエンドにしているXvfbが無いと言われて怒られる. /usr/X11/bin/xvfb にあるらしいんだけど、確かにない. ubuntuだったらapt-getで手に入るみたいだけどOSXについては直接の情報が無かった.
調べた結果、XQuartzに同梱されているみたいなので、これをダウンロード(dmgパッケージ)、インストールした.

お試し開始

ここまでやってようやっとお試しできるorz.
昨日作った、backboneを用いたアプリのスクリーンショットを撮るテストスペックを書いてみる.
ほぼこちらのサンプルを参考に作った.
yukaary$ rake features
# language:ja
機能: 指定したURLのスクリーンショットを撮る

  シナリオ: 表示してスクリーンショット.               # features/sample.feature:3
    前提"http://localhost:9000/"を表示    # features/step_definitions/steps.rb:10
    ならばスクリーンショットを取得し"yukaary.png"に保存 # features/step_definitions/steps.rb:13

1 scenario (1 passed)
2 steps (2 passed)
0m3.012s
やったぜ. テストを実行したディレクトリの直下にyukaary.pngが生成された. 中身を確認してみるとdiv要素”main-contents”の中身が空っぽ(本来ならテキストがあってBackbone.Viewで描画させている). でも一応スクリーンショットを取れた.
これからテストらしいテストを書いてみる.

Capybara x Cucumberの感想

これ、テスト手順(plain test)をCapybaraに認識させるために、ペアになるDSLの定義を書かないといけないのかな.
sample.feature
# language:ja
機能: 指定したURLのスクリーンショットを撮る
  シナリオ: 表示してスクリーンショット.
    前提 "http://localhost:9000/"を表示
    ならば スクリーンショットを取得し"yukaary.png"に保存
steps.rb
...
include Capybara::DSL
前提 /\"(.+)\"を表示/ do |url|
  visit(url)
end
ならば /スクリーンショットを取得し\"(.+)\"に保存/ do |filename|
  page.save_screenshot "./#{filename}"
end
steps.rbには、このまま他の手順(ならば)を追記していけそうだけど、DSLの管理がどうしても必要になりそう. 誰かデフォルトでそういうのを提供していそうだから、探してみようかな.
機械的に実行できるテストを日本語でそれっぽく記述できるのはとてもいいと.

Cucumberの日本語定義一覧.

yukaary$ cucumber --i18n ja
      | feature          | "フィーチャ", "機能"                                  |
      | background       | "背景"                                           |
      | scenario         | "シナリオ"                                         |
      | scenario_outline | "シナリオアウトライン", "シナリオテンプレート", "テンプレ", "シナリオテンプレ" |
      | examples         | "例", "サンプル"                                    |
      | given            | "* ", "前提"                                     |
      | when             | "* ", "もし"                                     |
      | then             | "* ", "ならば"                                    |
      | and              | "* ", "かつ"                                     |
      | but              | "* ", "しかし", "但し", "ただし"                       |
      | given (code)     | "前提"                                           |
      | when (code)      | "もし"                                           |
      | then (code)      | "ならば"                                          |
      | and (code)       | "かつ"                                           |
      | but (code)       | "しかし", "但し", "ただし"                             |

Saturday, June 28, 2014

Hubot x Redmine in OSX 10.9.2

HubotのRedmineスクリプトを試してみたいので嫌だけど渋々OSXにインストールする.

インストール手順

載ってた.

http://www.redmine.org/projects/redmine/wiki/RedmineInstallOSXMavericksServer

毎回苦労するPassengerさんの設定

Please edit your Apache configuration file, and add these lines:

   LoadModule passenger_module /Users/yukaary/.rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/passenger-4.0.45/buildout/apache2/mod_passenger.so
   <IfModule mod_passenger.c>
     PassengerRoot /Users/yukaary/.rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/passenger-4.0.45
     PassengerDefaultRuby /Users/yukaary/.rbenv/versions/2.1.1/bin/ruby
   </IfModule>

After you restart Apache, you are ready to deploy any number of web
applications on Apache, with a minimum amount of configuration!

Press ENTER to continue.
Deploying a web application: an example

Suppose you have a web application in /somewhere. Add a virtual host to your
Apache configuration file and set its DocumentRoot to /somewhere/public:

   <VirtualHost *:80>
      ServerName www.yourhost.com
      # !!! Be sure to point DocumentRoot to 'public'!
      DocumentRoot /somewhere/public    
      <Directory /somewhere/public>
         # This relaxes Apache security settings.
         AllowOverride all
         # MultiViews must be turned off.
         Options -MultiViews
         # Uncomment this if you're on Apache >= 2.4:
         #Require all granted
      </Directory>
   </VirtualHost>

And that's it! You may also want to check the Users Guide for security and
optimization tips, troubleshooting and other useful information:

やっぱり苦労して意味不明なのでRedmine付属の簡易サーバでやることにする. Webrick様ステキ!!

yukaary$ sudo ruby script/rails server webrick -e production

http://localhost:3000 でアクセスできる.

ちなみにOSX 10.9だとApache2がプリインストール済み.

  設定ファイル: /private/etc/apache2/
  Document Root: /Library/WebServer/Documents/
  
今回,Redmine 2.5.1をダウンロードしてDocument Rootの下に展開したが、別にApacheで動かさないのでここである必要は全くないorz.

Hubot Redmine

プロジェクトの活動概要を取得するコマンドを追加しようとした. 本当に最低限は動くようにしたが欠点が…

Hubot> hubot show project's activity
  • REST APIとATOM(RSS)のTOKENが別物
  • ActivityはJSON形式じゃないでござる
    • XMLをパースできないこともないけどメンドイ…

とりあえずこれでいいか…暇があったらActivityコマンドをまともにしてみよう.

Yeoman Backbone generator: scaffolding has been broken

壊れててまともに動かない件…

https://github.com/yeoman/generator-backbone/issues/268

なんか一部の処理がユーザの入力をすっ飛ばしているように見える. そして全ての生成ディレクトリ/ファイルがtest/ディレクトリ下に投入されていく.

でも

yukaary$yo backbone:view myview

とかするとなぜかappディレクトリが正しい位置にできたりして\(^0^)/状態…

一応対策でtest下に誤って配置されたと思われるファイル/ディレクトリを元の位置に戻してあげたら動いていはいるみたい.

使い勝手の学習

なんとか動くようにした(テストが動かなさそうだけど)ので使い勝手を確認していく.

yukaary$ yo backbone:router <name>
yukaary$ yo backbone:view <name>
yukaary$ yo backbone:collection <name>
yukaary$ yo backbone:model <name>

上のコマンドでBackboneの構成要素を適切な位置に追加していくことができる.

順番的には

  • router
  • view
  • collection, model

だろうか.

ただ、追加しただけでは利用可能にならないのでmainルーチン的なcoffeeファイルに追加した要素を追記していかなければならない等、微妙に使い勝手が悪い. そういうものなのかな?

  • router -> app/scripts/main.coffee
  • view -> 上で追加したrouter(app/scripts/routes/main.coffeeとか)

参考URl

http://albatrosary.hateblo.jp/entry/2014/02/05/172603

Hubot

hubot

本当はCapybara, Cucumberの使用方法を確認するつもりだったけど、こっちのが面白そうなので試してみる.

インストール & 準備

yukaary$ npm install -g hubot
yukaary$ hubot --create turtle

マイクラMODに登場するタートルを連想してしまったので、そんな名前にしてみるた.

hubotのスクリプト

このサイトで大量に管理されているので、自分の用途に合うものがあるかまず探してみる.

https://github.com/github/hubot-scripts

参考サイト

http://gihyo.jp/dev/serial/01/hubot/0002

Friday, June 27, 2014

続 Coffee Script

関数の呼び出し.

hello = (_option) ->
  "hello #{if _option? then _option else ''} world"

console.log ">function"
console.log hello
console.log hello.name
console.log hello()
console.log hello "cruel"
実行結果.
>function
[Function]

hello  world
hello cruel world
引数を指定しなくても関数をコール出来るので、hello, hello(), hello “cruel”がどんな結果を返すか見てみたら…
関数を呼び出しているのは
  • hello()とhello “cruel”
関数そのものを指しているのは
  • hello
見た目の共通性ではhello, hello “cruel”が同じように見えるのがたまらない…

それらしい書き方

本家のリファレンスを訳してくれてる人がいたー!
http://blog.h13i32maru.jp/entry/20120119/1326955300
プログラム例がだいぶ分かりやすい.

関数の中に関数

play = (title) ->
  @title = title
  console.log("play ")
  mode = (_mode) ->
    console.log "  " + @title + " with " + _mode

play("yukarin world")("single")

makiworld = play "makimaki no sekai"
makiworld("multi")
playが最後にsingle = -> … を実行するので返ってくるのはmode関数本体.
これを意図通り実行するには関数呼び出し()を2回繰り返す必要がある. 2パターンで試してみてどちらも期待する結果.

関数束縛

関数定義の際に「=>」を使うことで関数呼び出し元をthisに固定できる. 生成されたjavascriptを見ると何やってるかおおよそ理解. 上と同じく関数をネストさせて呼び出し元のthisをキープしている.
coffee script
bindplay = (title) ->
  @title = title 

  console.log("play ")
  mode = (_mode) =>
    console.log "  " + @title + " with " + _mode

play = (title) ->
  @title = title
  console.log("bind play ")
  mode = (_mode) ->
    console.log "  " + @title + " with " + _mode
javascript
  bindplay = function(title) {
    var mode;
    this.title = title;
    console.log("play ");
    return mode = (function(_this) {
      return function(_mode) {
        return console.log("  " + _this.title + " with " + _mode);
      };
    })(this);
  };

  play = function(title) {
    var mode;
    this.title = title;
    console.log("bind play ");
    return mode = function(_mode) {
      return console.log("  " + this.title + " with " + _mode);
    };
  };
具体的な違いの確認はブラウザ込み(eventhandler)でやらないと駄目かなぁ. htmlカキタクナイ.

Coffee Scrupt

Coffee Script

故あってcoffee scriptを学習中.
javascriptのわけわかめな部分を解消するのがメリット?ということだけど、こいつもなかなか謎の動きをする気がする.
色々なやり方で関数をコール、問題がある場合に2通りの動き方をするのがとても怖い.
  • 例外が投げられて明示的に落ちる.
  • undefinedになる.
これはもう、慣れるしかないね…

サンプルコード

class Voiceroid
  @version : 'plus'
  name : 'yukarin'
  constructor: (_name) ->
    @name = _name if _name?
  say: (word) ->
    @name + ':' + word
  @what_version: ->
    'This version is ' + @version

console.log Voiceroid.name

# class variable
console.log 'Voiceroid.version : ' + Voiceroid.version
console.log 'Voiceroid.waht_version() : ' + Voiceroid.what_version()
console.log 'Voiceroid.what_version : ' + new Voiceroid().what_version
console.log 'new Voiceroid().version : ' + new Voiceroid().version

# error
#console.log new Voiceroid().what_version()
#console.log Voideroid.name()
#console.log Voiceroid.version();

console.log new Voiceroid().say 'hello, hello'
console.log new Voiceroid("makimaki").say 'yukarin, can you hear me?'
実行結果.
yukaary% node js/class.js
Voiceroid
Voiceroid.version : plus
Voiceroid.waht_version() : This version is plus
Voiceroid.what_version : undefined
new Voiceroid().version : undefined
yukarin:hello, hello
makimaki:yukarin, can you hear me?

Javascript

JavaScript開発環境

いちいちブラウザを開いてデバッグコンソールでJavascriptの動作確認をするのが面倒なのでnode.jsでいいと思った.

Coffee Script

こちらのサイトを参考に手軽なコンパイル環境を構築.
http://tekkoc.tumblr.com/post/36510565810/coffeescript
yukaary$ npm install -g coffee
# 普通にファイル指定してコンパイル.
yukaary$ coffee --compile coffee/sample.coffee
# cofeeディレクトリ以下のファイルを継続してコンパイル.
yukaary$ coffee -o public/js -w coffee/
cofee単体でもcofee/ディレクトリ下のcoffeeスクリプトを自動でコンパイルしてpublic/jsの下に置いてくるようできる. やったぜ.

Grunt

gruntを使っても同じようにあるディレクトリに配置したcoffeeスクリプトをjavascriptに継続的にコンパイルできる. ただし、npmで色々とインストールが必要だったり、設定ファイル(Gruntfile.js)を書いたりと結構大変.
# grunt command line interface
yukaary$ npm install -g grunt-cli
yukaary$ which grunt
/Users/yukaary/.nvm/v0.10.29/bin/grunt
# generate empty package.json
yukaary$ npm init
# install grunt kinds
npm install grunt --save-dev
npm install grunt-contrib-coffee --save-dev
npm install grunt-contrib-watch --save-dev
# create Gruntfile.js
vim Gruntfile.js
====================================
// 'use strict';
module.exports = function(grunt) {
  grunt.initConfig({
    watch:{
      coffee: {
        tasks: 'coffee',
        files: ['src/**/*.coffee']
      }
    },
    coffee: {
      compile: {
        files:[{
          expand: true,
          cwd: 'src/',
          src: ['**/*.coffee'],
          dest: 'js/',
          ext: '.js'
        }]
      }
    }
  });
  grunt.loadNpmTasks('grunt-contrib-coffee');
  grunt.loadNpmTasks('grunt-contrib-watch')
====================================
  • 「npm init」でpackage.jsonを作成.
  • 続く「npm install xxx –save-dev」でnpmパッケージを直下のディレクトリにインストール、同時にpackage.jsonに追記させていく.
  • grunt-contrib-coffeeでcoffeescript開発環境
  • grunt-contrib-watchで自動コンパイル環境
これにテスト用のnpmパッケージを追加で入れていくくらいなら最初からYeomanでいい気がしてきた…今回はcoffee scriptの勉強が目的なので、自動コンパイルが出来るまででいい.

Tuesday, June 24, 2014

node.js x WebSocket

node.js x WebSocket

node.jsでWebSocketを使った通信を実装しているアプリを試してみたくて色々と調べ回っている.

stomp-websocketというのが使い勝手が良さそうな感じ. 少し調べてみるとSTOMPというテキストベースのメッセージ交換プロトコルに少し手を加えてWebSocketプロトコルをさばけるようにしたとか.

http://jmesnil.net/stomp-websocket/doc/

作者の方がgithubでstomp.js(node.jsのstomp用コンポーネント)とそのサンプルを公開していたのでこれを使ってみる.

RabbitMq Web-Stomp Plugin

ゆかりさん的にRabbitMqを使わざるを得ないと思った. 後悔はしない.

RabbitMqを公式サイトからダウンロード.

https://www.rabbitmq.com/install-standalone-mac.html

解凍してとりあえず${home-directory}/Applicationsに展開.
Web-Stompプラグインを有効にする.

yukaary$ sbin/rabbitmq-plugins enable rabbitmq_web_stomp
The following plugins have been enabled:
  amqp_client
  rabbitmq_stomp
  cowboy
  sockjs
  rabbitmq_web_stomp
Plugin configuration has changed. Restart RabbitMQ for changes to take effect.

RabbitMQ起動, ステータスを確認.

yukaary$ sbin/rabbitmq-server -detached
Warning: PID file not written; -detached was passed.
yukaary$ sbin/rabbitmqctl status
Status of node rabbit@yukaary ...
[{pid,2080},
 {running_applications,
     [{rabbitmq_web_stomp,"Rabbit WEB-STOMP - WebSockets to Stomp adapter",
          "3.3.3"},
      {rabbitmq_stomp,"Embedded Rabbit Stomp Adapter","3.3.3"},
      {rabbit,"RabbitMQ","3.3.3"},
      {os_mon,"CPO  CXC 138 46","2.2.13"},
      {mnesia,"MNESIA  CXC 138 12","4.10"},
      {amqp_client,"RabbitMQ AMQP Client","3.3.3"},
      {xmerl,"XML parser","1.3.4"},
      {cowboy,"Small, fast, modular HTTP server.","0.5.0-rmq3.3.3-git4b93c2d"},
      {sockjs,"SockJS","0.3.4-rmq3.3.3-git3132eb9"},
      {sasl,"SASL  CXC 138 11","2.3.3"},
      {stdlib,"ERTS  CXC 138 10","1.19.3"},
      {kernel,"ERTS  CXC 138 10","2.16.3"}]},
 {os,{unix,darwin}},
 {erlang_version,
     "Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:30] [hipe] [kernel-poll:true]\n"},
 {memory,
     [{total,38444128},
      {connection_procs,5616},
      {queue_procs,5616},
      {plugins,212184},
      {other_proc,14334736},
      {mnesia,61488},
      {mgmt_db,0},
      {msg_index,29816},
      {other_ets,1168456},
      {binary,22440},
      {code,17366130},
      {atom,654217},
      {other_system,4583429}]},
 {alarms,[]},
 {listeners,[{clustering,25672,"::"},{amqp,5672,"::"},{stomp,61613,"::"}]},
 {vm_memory_high_watermark,0.4},
 {vm_memory_limit,1463938252},
 {disk_free_limit,50000000},
 {disk_free,373913059328},
 {file_descriptors,
     [{total_limit,156},{total_used,4},{sockets_limit,138},{sockets_used,2}]},
 {processes,[{limit,1048576},{used,250}]},
 {run_queue,0},
 {uptime,19}]
...done.

STOMPプラグンは有効になっているかな?ポートはデフォルトだと15674. http://127.0.0.1:15674/stompにブラウザでアクセスしてみるとWelcome to SockJS!と出てくる.

node.js側からここにアクセスできればいいのかな?

この後頑張ってなんとか動作するようにしたいわゆるチャットプログラム(若干変だけど)…後日まとめる. いわゆる「stomp over websocket」にする必要があった.

コマンド覚え書き

RabbitMqに専用のアカウントを追加.

yukaary$sbin/rabbitmqctl add_user yukaary yukaary
yukaary$sbin/rabbitmqctl set_permissions -p / yukaary  ".*" ".*" ".*"

参考サイト

Monday, June 23, 2014

LINCXの前に

LINCXの前に

Erlang

LINCXが記述されているErlangについて少し知っておくことにする,
関数型言語らしい…最近Haskellを少し齧ったなぁ.

OSX用のパッケージは以下のURLから入手できる.

https://www.erlang-solutions.com/downloads/download-erlang-otp

インストール後、この手の言語にはだいたいあるirbみたいのを探してみたところerlというのを発見.

yukaary$ which erl
/usr/local/bin/erl
yukaary$ erl
Erlang R16B01 (erts-5.10.2) [source-bdf5300] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.2  (abort with ^G)
1> 

よしよし. さっそく使っていこう. IBMのサイトの簡単なイントロ(https://www.ibm.com/developerworks/jp/opensource/library/os-erlang1/)を参考に色々試してみる.

  1. 一行の終わりは「.」で区切る.
  2. 指数を利用できる. 10.9E-2., 10.11E5.
  3. アトム. 静的リテラルまたは定数. 有効な演算は比較だけ
12> 1 == 1.
true
13> 0 == 1.
false
14> 1 == "1".
false
15> "1" == "1".
true
16> 1 == '1'.
false
17> yukaary == yukaary.
true
18> yukaary == 'yukaary'.
true
19> yukaary == "yukaary".
false
20> 'yukaary' == "yukaary".
false
21> "yukaary" == "yukaary".
true
22> 'y' > 'm'.
true
23> 'y' < 'm'.
false
24> y > m.
true
25> y > "m".
false

シングルクォーテーションとダブルクォーテーションの扱いを間違えると死ねそう.
* 文字列の場合
- シングルクォーテーションありと無しは同じ扱い
- ダブルクォーテーションありと無しは別もの扱い -> ストリング値、だそうな.
* 数値の場合
- シングル、ダブル クォーテーションともに別物扱い.

4. タプル

25> {yukaary, "yukaary craft", 2014, "yukaary.craft@gmail.com"}.
{yukaary,"yukaary craft",2014,"yukaary.craft@gmail.com"}
26> element(2, {yukaary, "yukaary craft", 2014, "yukaary.craft@gmail.com"}).
"yukaary craft"
27> element(10, {yukaary, "yukaary craft", 2014, "yukaary.craft@gmail.com"}).
** exception error: bad argument
     in function  element/2
        called as element(10,
                          {yukaary,"yukaary craft",2014,
                                   "yukaary.craft@gmail.com"})
28> element(1, element(5, {yukaary, "yukaary craft", 2014, "yukaary.craft@gmail.com", {friend, "makimaki", "zunchan"}})).
friend

ほうほう…やっぱり基本的なところはhaskellと変わらない.

31> {yukaary, maki} == {yukaary, maki}.
true
32> {yukaary, maki} == {"yukaary", maki}.
false
33> {'yukaary', maki} == {"yukaary", maki}.
false
34> {'yukaary', maki} == {'yukaary', maki}.
true

タプルの比較は先頭要素から順に適用される.

5. リスト

60> [1,2,3,4,5].
[1,2,3,4,5]
61> [72,101,108,108,111].
"Hello"
62> [72,73,74,75,76].    
"HIJKL"
63> [$Y,$u,$k,$a,$a,$r,$y].
"Yukaary"

えええ?文字列はリスト扱いらしいけど、数字のリストの結果が文字列に変換されるのはどうなの.

リストの結合

「++」を使う.

23> "yukaary" ++ "makimaki".
"yukaarymakimaki"

リストからの要素除去

「–」を使う.

1文字ずつ先頭から合致したものを除去していくみたい. 下の例だと「 」(空白)2つを除去するために” “を引いてやる必要がある. 間にxを入れても同じ結果ということは順序はどうでもよさげ.

45> Voiceroids -- " ".   
"yukarimaki zunko"
46> Voiceroids -- "   ".
"yukarimakizunko"
47> Voiceroids -- " x  ".
"yukarimakizunko"

束縛変数

束縛変数なるものを定義できる. 一度値を設定したら2度と更新できないみたいなので変…数?という感じだけど.

36> Yukari = "Yukari Yuzuki".
"Yukari Yuzuki"
37> Maki = "Maki Tsurumaki".
"Maki Tsurumaki"
38> Zun = "Zunko Touhoku".
"Zunko Touhoku"
39> b().
Maki = "Maki Tsurumaki"
Yukari = "Yukari Yuzuki"
Zun = "Zunko Touhoku"
ok
  • b(). 現在の束縛変数をリストアップ.
  • f(). 束縛変数を解放.

一度変数を定義すると型が確定するので以降その変数を用いた計算に制限ができる.

2> Value = 100.
100
3> Total = Value + 8.
108
4> Total = Value + "8".
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  +/2
        called as 100 + "8"

タプルと変数を用いて、タプルから特定の要素を引き出せる.

5> {contact, Name, Email} = {contact, "Yukaary", "yukaary.craft@gmail.com"}.
{contact,"Yukaary","yukaary.craft@gmail.com"}
6> Name.
"Yukaary"
7> Email.
"yukaary.craft@gmail.com"

最初のcontactはマッチング用.

リストでも同様の処理を適用できる.

18> [A,B|C] = [yukaary, maki, zunko, futago].
[yukaary,maki,zunko,futago]
19> A.                                       
yukaary
20> B.
maki
21> C.
[zunko,futago]

変数は一度限りしか代入できないから、プログラミングとしては一度限りの変数(または極力変数を使わずに)を駆使しながら処理を記述していけばいいのかな.

アトムとストリング

  • アトムは原子的という意味合いからそれ以上分割できない.
  • ストリングは中身はリストだから分割したりする余地がある.
66> [H|T] = "yukaary".
"yukaary"
67> H.
121
68> [121].
"y"
69> T.
"ukaary"

今度は「y」が対応する数値になったでござる.

1> [H|T] = [yukaaary, makimaki, zundaa].
[yukaaary,makimaki,zundaa]
2> H.
yukaaary
3> f().
ok
4> [H|T] = 'yukaary'.                   
** exception error: no match of right hand side value yukaary
5> [H|T] = yukaary.  
** exception error: no match of right hand side value yukaary
6> [H|T] = ["yukaary"|"makimaki"].
["yukaary",109,97,107,105,109,97,107,105]
7> H.                             
"yukaary"
8> [H2|T2] = H.
"yukaary"
9> H2.
121

atomは分割できないので例外が発生する.
文字列同士を「|」で結合させたら入れ子のリストが出来ることが判明.

Sunday, June 22, 2014

docker 1.0, OSX mavericks, part2

docker 1.0, OSX mavericks, part2

できたー!暫定だけどコンテナを252個以上立ち上げる事に成功.
色々探しまわって行き着いたのは以下の要因.

  • OSXのdockerインストーラはdockerのホストマシンをVirtuallboxを使って仮想マシンとして立ち上げている.
    • この仮想マシンがまず問題で、最低限dockerを実行するのに必要なものしか入っていない.よって細かい設定ができない??
    • dockerプロセスが開けるファイルの上限を設定したいのに/etc/init/docker.confがないので涙目になる.
  • 252個でコンテナが打ち止めになるのは1プロセスが開くことのできるデフォルトのファイル数上限が1024だから
    • /proc/${dockerのプロセスID}/limitsを見ると「Max open files 1024 4048 files」ってラインがあって、こいつの上限を上げる必要がある
    • ※ちょうどコンテナ数=253個目あたりでファイル数の上限に到達している.

無理やり動かすにはどうするか考えた結果、起動スクリプト「/etc/init.d/docker」を弄ってopen filesの上限を引き上げ、動作確認することにする。

動作確認

以下の一行をstart()の上のほうに追加する.

start() {
    DOCKER_DIR=/var/lib/docker
    mkdir -p "$DOCKER_DIR"

    ulimit -n 65535 # <-- これ!

続いて既存のdockerプロセスを止めるために何度か/etc/init.d/docker stop
するも、止まる気配がない…のでプロセスIDを調べてkill -9 ${プロセスID}コマンドで強制停止.(仮想マシンだから壊れてもいいくらいのでノリ)

/etc/init.d/docker startで再起動.
プロセスIDを確認して前回と異なるプロセスIDで立ち上がったことを確認. open filesに関する制限を再確認.

$ less /proc/14311/limits
...
Max open files            65535                65535                files
...

いいねいいね.

続いて前回のhello world x 1000(今回は300に抑えた)を実行.
この前に前回のコンテナの情報をdocker stop, docker rmで削除.

$ ./run-multiple-docker.sh 
...
2c3c712abfa5d1aee070ad9cddaddedbf9d7e1d983c144125b7847044a925e33
b06d57ce41f7bb2572bd134ffa58ae1d23d5595f0ef09de9c3163921c36cadbf
2014/06/22 20:45:41 Error: Cannot start container b06d57ce41f7bb2572bd134ffa58ae1d23d5595f0ef09de9c3163921c36cadbf: file exists
...
d87b5344f2e3cfea5bc623123f01554bfa7c8fe34dc31b0fc3d2c51a7a408594
e3519356e2b403a055dfaea0952b46d555af91711b1cb3f27b3350b057bf2ad4
22ee510ef1b0f7cef812a8a42e970a38a869c1afb47369699eab72f84aa7e5d2
f0696f17d8519f44dee365af3732de7df7aca7a6e63cc8a2f8c36c146ffe3a54
52f707fecd5be873d50a971031fab8488649f82b10871241b2558470a3a5b569
2014/06/22 20:50:25 Error: Cannot start container 52f707fecd5be873d50a971031fab8488649f82b10871241b2558470a3a5b569: Cannot find child for /yukaary_260
6a2aceb79c041ae4cbe44b7878503cc212dfba26abc78748a5be086c2e066b8b
54c28d69f9f97bae01f50aae4707192d417c35dd633a1c28355a8ba806bad21a
...
$ docker ps | wc -l
     299

やったぜ.

2つこけてるコンテナがいるのはなんだろな.

  • そのハッシュ値のコンテナに関するファイルが既にある
  • yukaary_260に関連する子要素が見つからない

なんとなくだけど、次のコンテナを立ち上げる前に1秒くらい待機させたほうがいいかも.

ここでdockerホストマシンで下のコマンドを打つと、こんなのが299個続く.

$ ps aux | grep hello
  463 root     /bin/sh -c while true; do echo hello world; sleep 1; done
  548 root     /bin/sh -c while true; do echo hello world; sleep 1; done
  588 root     /bin/sh -c while true; do echo hello world; sleep 1; done
  ...

単にhello worldさせているだけだと「ひたすら回りくどいやり方でhello worldするプログラムを299個動かしている」だけ.これを見てfull VMじゃなくただのOSコンテナなんだなーと実感.見た目上は全く同じように見えるけど、表に出ていないだけでカーネルの名前空間がきっと違う.

まとめ

ちなみに問題が残っていてOSX側でboot2docker restartとやると,/etc/init.d/dockerに追記したulimitが消える.きっとvagrantとかchef的な何かがプロビジョニングしているためと信じているが、その設定がどこにあるのか分からない .boot2dockerもdockerもバイナリだし、使い勝手が地味に悪い…

ここまで来て「これ、普通にvirtualboxでubuntu 14.04動かしてapt-getでdockerインストールしたほうがマシなんじゃ…?」と気づいた.

次はdockerを進める前にLINCXとかいうSDN スイッチを弄ってみよう…名前がかっこいい!

参考

まさかのelastic_searchで同じファイル数上限問題に遭遇している方がいて,その方の情報が参考になった.

Friday, June 20, 2014

Markdown x Blogger

Markdown記法で記事を書いてBloggerに投稿

できないかな?と思って調べていたら記事があった. 
  • http://nushu123.blogspot.jp/2013/11/markdownblogger.html
  • http://tizio1976.blogspot.jp/2013/11/chrome-stackedit-markdown-blogger.html
コードやXML等を扱いたかったからすぐに導入を始める.

Thursday, June 19, 2014

docker 1.0, OSX mavericks

docker 1.0, OSX mavericks


今更だけどdockerという面白そうなLinux OSコンテナがある。最近1.0が出たので色々と試してみる。どこぞの外人さんが「dockerの利点としてカーネルの名前空間を区切る事で隔離された環境を形成、full VMと違ってメモリを大量確保しないから、極端な話、1000VM同時に立ち上げることができるよ」(stackoverflow)って書いてたけど、本当にそんなこと出来るのか...

dockerインストール

まずは本家サイトを見ながらインストールを試してみる.

  • OSX installerをダウンロード
  • インストール時の選択肢は全部デフォルト
  • 続いてLaunchpadからboot2dockerを実行
    • 自動で仮想ネットワークでバイスと仮想マシンのイメージを作り始めた.※1
  • 続いてコンテナ上でhello worldを実行させる
    • イメージダウンロードしてないんだけど動くの?と思っていたらコマンド打った瞬間にダウンロードを始めるdockerさん. ありがたいような、ありがたくないような.※2


※1
インストール時のメッセージ.

2014/06/20 00:21:35 To connect the Docker client to the Docker daemon, please set:
2014/06/20 00:21:35     export DOCKER_HOST=tcp://:2375

上の気になったのでインストール後のDOCKER_HOSTの値を見てみる.

imac:~ yukaary$ echo $DOCKER_HOST
tcp://192.168.59.103:2375

ポート番号は合ってる,

※2
最初のdockerコマンド実行時の出力.

imac:~ yukaary$ docker run ubuntu echo hello world
Unable to find image 'ubuntu' locally
Pulling repository ubuntu
e54ca5efa2e9: Download complete
511136ea3c5a: Download complete
d7ac5e4f1812: Download complete
2f4b4d6a4a06: Download complete
83ff768040a0: Download complete
6c37f792ddac: Download complete
hello world


インストール後の確認

ubuntuのバージョン

インタラクティブモードでコンテナの中に入ってダウンロードされたコンテナイメージのubuntuバージョンを確認.

imac:~ yukaary$ docker run -t -i ubuntu /bin/bash
root@99f23ba9e0eb:/# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04 LTS"

14.04!一番新しいバージョンじゃまいか.
ホスト側から現存するイメージをリストアップさせる. 先ほどダウンロードしたubuntuが1つ.
imac:~ yukaary$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              latest              e54ca5efa2e9        17 hours ago        276.5 MB
 そもそもこのコンテナイメージはどこに配置されてる?と思ってdocker infoしてみる
imac:~ yukaary$ docker info
Containers: 2
Images: 6
Storage Driver: aufs
Root Dir: /mnt/sda1/var/lib/docker/aufs
Dirs: 10
Execution Driver: native-0.2
Kernel Version: 3.14.1-tinycore64
Debug mode (server): true
Debug mode (client): false
Fds: 10
Goroutines: 11
EventsListeners: 0
Init Path: /usr/local/bin/docker

Root Dir: /mnt/sda1/var/lib/docker/aufsとかないんだけど...と色々調べた結果、docker本体はVirtualBoxが実行しているboot2docker-vmという名前の仮想マシンの中にいることが分かる 「dockerインストール」のところでboot2dockerをインストールしていたときに自動で生成された模様...

  • Vagrant用にVirtualBoxをインストールしていたためか、エラーも無くいつの間にか 作られていたorz


boot2docker sshでboot2docker-vmの中に入りRootDirが存在することを確認.

imac:~ yukaary$ boot2docker ssh
Warning: Permanently added '[localhost]:2022' (RSA) to the list of known hosts.
## .
## ## ## ==
## ## ## ## ===
/""""""""""""""""\___/ ===
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
\______ o __/
\ \ __/
\____\______/
_ _ ____ _ _
| |__ ___ ___ | |_|___ \ __| | ___ ___| | _____ _ __
| '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__|
| |_) | (_) | (_) | |_ / __/ (_| | (_) | (__| < __/ |
|_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_|
boot2docker: 1.0.0
master : 16013ee - Mon Jun 9 16:33:25 UTC 2014
docker@boot2docker:~$ ls /mnt/sda1/var/lib/docker/aufs/
diff/ layers/ mnt/
docker@boot2docker:~$ ifconfig
...
eth1      Link encap:Ethernet  HWaddr 08:00:27:1C:12:2F
          inet addr:192.168.59.103  Bcast:192.168.59.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe1c:122f/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:515 errors:0 dropped:0 overruns:0 frame:0
          TX packets:441 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:41764 (40.7 KiB)  TX bytes:96333 (94.0 KiB)



boot2docker-vm上でifconfigした時のeth1のIPアドレスがホストマシンで「echo $DOCKER_HOST」したときのIPアドレスと一致しているから、そういうことなのね...


この場合、dockerをデーモンモードで起動したときのプロセスはホストマシン、boot2docker-vmのどちらに作られる?

ホストマシンで探してみる(いい加減)
imac:~yukaary$ ps aux | grep docker
youichir         1564   0.3  0.0  2442000    624 s003  S+    1:08AM   0:00.01 grep dodcker.
うーん、いない。boot2docker-vmは?

docker@boot2docker:~$ ps aux | grep docker
  913 root     /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// -H tcp://0.0.0.0:2375
...
やっぱりこっちか. 前に試した時はホストマシンで直接dockerが動いていた気がするんだけどなぁ.  VM on VMじゃないからまあいいか...性能を気にする場合はKVM上でdockerを動かせたほうがいいかも.

よーし、じゃあ後はただひたすら「hello world」を出力し続けるだけのコンテナを1000個立てちゃうぞー.

すくりぷと.
#bin/bash
for i in `seq 1 1000`
do
docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
done

実行前のメモリ使用率.

docker@boot2docker:~$ free
             total         used         free       shared      buffers
Mem:       1024976       631448       393528            0        15436
-/+ buffers:             616012       408964
Swap:       194000            0       194000


実行時、凄い勢いでコンテナが立ち上がっていくが、コンテナ数が100いかないうちにだんだん遅くなってくる.

ちなみになんかこんなエラーが複数回出てたり.

  •  IDだか名前(明示的に指定しない場合はランダムに設定されてる)だかが被ったかな? 
2014/06/20 01:31:30 Error: Cannot start container ebeaba1c9464ffd101a78e80b609bf04c743ade4255277d38b51282581402352: file exists

そのうち全部エラーになった. しかも止まらん...。ファイルを開き過ぎって言われてるからなんかのパラメータいじれば直るかなぁ.

2014/06/20 01:36:40 Error: open /mnt/sda1/var/lib/docker/aufs/layers/e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37: too many open files
2014/06/20 01:36:40 Error: open /mnt/sda1/var/lib/docker/aufs/layers/e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37: too many open files
2014/06/20 01:36:41 Error: open /mnt/sda1/var/lib/docker/aufs/layers/e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37: too many open files
2014/06/20 01:36:41 Error: open /mnt/sda1/var/lib/docker/aufs/layers/e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37: too many open files

この時のコンテナ数をboot2docker側でカウント.
docker@boot2docker:~$ docker ps | wc -l
252
250個くらい.この時のfreeの結果.
docker@boot2docker:~$ free
             total         used         free       shared      buffers
Mem:       1024976       942604        82372            0        88060
-/+ buffers:             854544       170432
Swap:       194000         2448       191552


実行前とメモリ量の差分が300MBくらいだから大雑把にコンテナ1つあたりのメモリ使用量は1.2MBくらいだろうか? 単に「hello world」してるだけなんだけど...。

立ち上げたコンテナを一括で停止するコマンド.

これでおっけい. ちなみに開始するのは早いけど止まるのは遅いorz
imac:~yukaary$docker stop $(docker ps -a -q)


  • 今度はapache, sshd, mysqldなどを動かした環境で同じ実験をしてみる.
  • 仮想マシンboot2dockerのメモリは2Gまで増やす.
  • file exists, too many open filesエラーの原因を調べる.