昔は違った気がするのですが、手元の環境のautotest(ZenTest-3.10.0)とRSpec-1.1.4を使った状態で、テスト中に一度例外が発生した後に、全てのテストをgreenにすると、全てのテストがもう一回全部実行されるという現象が起きています。

全部テストを実行するのは大変なので、とりあえず、.autotest でパッチを当てて回避しました。

   1  class Autotest
   2    alias_method :handle_results_old, :handle_results
   3    undef_method :handle_results
   4  
   5    def handle_results(results)
   6      failed = results.scan(self.failed_results_re)
   7      completed = results =~ self.completed_re
   8  
   9      self.files_to_test = consolidate_failures failed if completed
  10  
  11      color = completed && self.files_to_test.empty? ? :green : :red
  12      hook color unless $TESTING
  13  
  14      #self.tainted = true unless self.files_to_test.empty?
  15      self.tainted = !self.files_to_test.empty?
  16    end
  17  end

L14-15行目が修正箇所です。

posted by Png genki on Sun 6 Jul 2008 at 11:46

メモもかねて。

MacBookにRailsの自動テスト環境を構築した

RAILS_ROOTでautotestを実行。起動時に全部のテストを実行するのでちょっとだけ重くなるかもしれません。それ以降はテストファイルやモデル、コントローラファイルなどを修正する度に該当のテストが走ります。

autotestを-fオプションを付けて立ち上げると、Fast startモードになり、最初に全部のテストを実行しなくなります。

posted by Png genki on Sun 6 Jul 2008 at 10:53

KagemushaでActiveRecordのfindなどのクラスメソッドをswap しようとすると、

   1  singleton method bound for a different object (TypeError)

というエラーが発生するという問題がありました。

このままでは困るので、とりあえず動くようにしてみました。 Unboundにしなければ問題は起こらないと思ったので、 instance_methodの代わりにalias_methodを使っています。

kagemusha_core_ext.rb

   1  require 'uuidtools'
   2  
   3  class Kagemusha #:nodoc:
   4    def swap #:nodoc:
   5      original_class_methods    = {}
   6      original_instance_methods = {}
   7  
   8      @class_methods.each { |name, proc|
   9        if proc
  10          begin
  11            # replace method
  12            #method = @meta.instance_method(name)
  13            method = random_name
  14            @meta.instance_eval { alias_method method, name }
  15            @meta.instance_eval { define_method(name, proc) }
  16            original_class_methods[name] = method
  17          rescue NameError
  18            # insert method
  19            @meta.instance_eval { define_method(name, proc) }
  20            original_class_methods[name] = false
  21          end
  22        else
  23          begin
  24            # remove method
  25            #method = @meta.instance_method(name)
  26            method = random_name
  27            @meta.instance_eval { alias_method method, name }
  28            @meta.instance_eval { undef_method(name) }
  29            original_class_methods[name] = method
  30          rescue NameError
  31            # nop
  32          end
  33        end
  34      }
  35  
  36      @instance_methods.each { |name, proc|
  37        if proc
  38          begin
  39            # replace method
  40            method = @klass.instance_method(name)
  41            @klass.instance_eval { define_method(name, proc) }
  42            original_instance_methods[name] = method
  43          rescue NameError
  44            # insert method
  45            @klass.instance_eval { define_method(name, proc) }
  46            original_instance_methods[name] = false
  47          end
  48        else
  49          begin
  50            # remove method
  51            method = @klass.instance_method(name)
  52            @klass.instance_eval { undef_method(name) }
  53            original_instance_methods[name] = method
  54          rescue NameError
  55            # nop
  56          end
  57        end
  58      }
  59  
  60      return yield
  61    ensure
  62      original_class_methods.each { |name, method|
  63        if method
  64          # replace method
  65          #@meta.instance_eval { define_method(name, method) }
  66          @meta.instance_eval { alias_method name, method }
  67          @meta.instance_eval { undef_method method }
  68        else
  69          # remove method
  70          @meta.instance_eval { undef_method(name) }
  71        end
  72      }
  73      original_instance_methods.each { |name, method|
  74        if method
  75          # replace method
  76          @klass.instance_eval { define_method(name, method) }
  77        else
  78          # remove method
  79          @klass.instance_eval { undef_method(name) }
  80        end
  81      }
  82    end
  83  
  84  private
  85    def random_name
  86      [UUID.random_128].pack("m").tr("=\n", '')
  87    end
  88  end

posted by Png genki on Sun 6 Jul 2008 at 05:22 with 2 comments

テストのC0カバレッジを上げるために、 何度もrcovを手で実行していたのですが、 テストの数が多くなってくると非常に時間がかかって面倒なため、 自動化するRakeタスクを作りました。

   1  namespace :rcov do
   2    desc 'Automate rcov'
   3    task :auto do
   4      interval = (ENV['INTERVAL'] || 10).to_f
   5      org_path = File.join(RAILS_ROOT, 'coverage', '*')
   6      pub_path = File.join(RAILS_ROOT, 'public', 'coverage')
   7      sh "mkdir -p #{pub_path}"
   8      loop do
   9        Rake::Task['spec:rcov'].execute nil
  10        sh "cp -Rf #{org_path} #{pub_path}"
  11        sleep interval
  12      end
  13    end
  14  end

使い方はRAILS_ROOTで以下のコマンドを実行するだけです。

   1  % rake rcov:auto INTERVAL=10

指定した時間間隔(秒)ごとにspec:rcovを実行します。 実行した結果はRSpecのデフォルトでは RAILS_ROOT/coverageに出力されるので、これを public/coverageに上書きコピーしています。 あとは、ブラウザで/coverageを見ればOKです。

public/coverageは間違ってデプロイしないように.gitignoresvn:ignoreに登録しておきましょう。

posted by Png genki on Sat 5 Jul 2008 at 03:43

I made a small utility library named Wormhole. The library enables us to communicate between caller and callee.

Here is a simple example of use.

   1  require 'rubygems'
   2  require 'wormhole'
   3  
   4  def foo(array)
   5    array << :foo # (1)
   6    Wormhole.throw array
   7    array << :baz # (3)
   8  end
   9  
  10  result = Wormhole.catch do
  11    foo []
  12  end.return do |array|
  13    array << :bar # (2)
  14  end
  15  puts result.inspect # => [:foo, :bar, :baz]

First, the block passed to Wormhole.catch, the caller, is evaluated. The foo method, the callee, throw an array object passing through the wormhole. Then the array is caught by the last block passed to a return method. A block parameter of the block is the array. Finally, the process goes back to the point 3 after the last block ends. A return value of the foo method is returned via the return method.

By using this utility, you can participate to a depth of a complicated system from a safer position.

At the end, you can install this utility from the GitHub by using gem command like this.

   1  % sudo gem sources -a http://gems.github.com
   2  % sudo gem install genki-wormhole

Have fun!

posted by Png takiuchi on Sat 5 Jul 2008 at 01:53 with 2 comments

メソッドやProcの内部で継続(Continuation)を実行した後に、 そのメソッドの戻り値がどうなるのか理解があやふやだったので実験。

前準備。

   1  a = nil
   2  foo = proc{callcc{|a|}; 1}
   3  bar = proc{foo.call; 2}
   4  baz = proc{a.call; 3}

実験開始。

   1  >> foo.call
   2  => 1
   3  >> baz.call
   4  => 1
   5  >> bar.call
   6  => 2
   7  >> baz.call
   8  => 2

なるほど。

posted by Png genki on Fri 4 Jul 2008 at 18:57

While people in the US move to FriendFeed or Plurk, Japanese move to Wassr.

Wassr is one of the most popular twitter clone service in Japan. It has stared service about year ago, now has almost all the same features of Twitter(like API, mobile version site...).

First I noticed some Dowango(runs NicoNico Douga) ppl go to wassr a couple of months ago while twitter is down. And now more and more ppl move to Wassr. Most of the people do multiple post to Twitter and Wassr, though.

Wassr

http://wassr.jp/ Japanese version
http://wassr.com/ English version
runs by
http://www.mobilefactory.jp/

posted by Png hibi on Fri 4 Jul 2008 at 11:16

ちょっと必要に駆られて、 Wormhole というライブラリを書きました。

350px-Worm3.jpg

インストールはGitHubから行えます。

   1  sudo gem sources -a http://gems.github.com
   2  sudo gem install genki-wormhole

メソッドの内部を実行中に、呼び出し元に一旦処理を戻し、 中断した場所から処理を継続する機能を提供します。

例えば、以下のようなプログラムに対して、

   1  require 'rubygems'
   2  require 'wormhole'
   3  
   4  def foo
   5    puts "foo"
   6    Wormhole.throw :foo => 'hello' do |data|
   7      puts data[:foo]
   8    end
   9    puts "bar"
  10  end
  11  
  12  Wormhole.catch do
  13    foo
  14  end.return do |data|
  15    puts data[:foo]
  16    data[:foo] = 'world!'
  17  end

結果として、以下のような出力を得ます。

   1  foo
   2  hello
   3  world!
   4  bar

処理を中断したいところで Wormhole.throw を呼び出して、 外側から Wormhole.catch で受け止めます。 catchが完了したら、Wormholeオブジェクトが返るので、 returnで中断箇所に戻ります。 Wormhole.throw はHashを受け渡すことができます(省略可能)。 また、catchブロックとreturnは上記の例のように続けて記述します。 catchの戻り値を適当な変数に代入して、後でreturn を呼び出すことも可能ですが、その場合、catchを抜けた直後から returnを呼ぶまでの区間が二度実行されてしまいます (この辺が最初に例外を使って実装してた理由ですね)

中断箇所から処理を再開するために callcc を使っています。 callccは面白いですね。

更新履歴

  • 2008/7/4 例外処理をやめてthrow/catchを使うようにしました。それにあわせて本文を修正しました。
posted by Png genki on Thu 3 Jul 2008 at 20:06 with 5 comments

以前笹田さんに、procのソースが見たいとお願いしてみたことがあったのですが、 それをRubyコードレベルで実現するライブラリを見つけたので紹介します。

proc_source.rb

I wrote this a while ago and it works by extracting a proc's origin file name and line number from its .inspect string and using the source code (which usually does not have to be read from disc) -- it works with procs generated in IRB, eval() calls and regular files. It does not work from ruby -e and stuff like "foo".instance_eval "lambda {}".source probably doesn't work either.

オリジナルはメールの添付ファイルとしてくっついているので、ソースを見たい場合は こちらから見るといいかもしれません。

使い方はこんな感じです、

   1  code = proc{puts "Hello World"}
   2  puts code.source #=> puts "Hello World"

これは久々に面白いものを見た気がします。

posted by Png genki on Thu 3 Jul 2008 at 13:43

以前、Repoが壊れたというのでjs2-modeが原因みたいなことを書いたのですが間違っていたみたいで、今朝また壊れました...js2-modeの中の人ごめんなさい。

また原因がわかったら記事を書こうと思います。 以前の記事は非公開にしましたのでご了承ください。

posted by Png satoko on Thu 3 Jul 2008 at 10:22