assert_gem, assert_ruby

実際の仕事でgemのバージョンによってテスト結果が違っている、ということで悩んだことがあり、gemやrubyのバージョンは自動テストでチェックするべきだなーって思いました。

こんな感じで書きたい。

require File.dirname(__FILE__) + '/../test_helper'

class EnvironmentTest < ActiveSupport::TestCase
  def test_environment
    assert_ruby("1.8.6")
    assert_gem("log4r", "1.0.5")
    assert_gem("mongrel", "1.1.x")
    if /java/ =~ RUBY_PLATFORM
      assert_gem("gettext", "1.90.x")
    else
      assert_gem("gettext", "1.90.x", "1.10.x")
    end
  end
end

xの部分は数字ならOK。>= とかでチェックできたらいいかもとも思ったんですけど、実際仕事で使う場合、そういう風に書きたいケースって少ないだろうなーって思いました。プロジェクトで使うバージョンを列挙した方が間違いない感じがします。


で、そんな風に書けるようにするコードはこちら

class Test::Unit::TestCase
  def version_str(*versions)
    versions.map do |version|
      version.gsub(/\./, "\\.").gsub("x", "\\d+")
    end
  end
  
  def assert_ruby(*versions)
    versions = version_str(*versions)
    regexp = Regexp.union(*versions.map{|version|
        Regexp.new("^#{version}$")
      })
    assert_match(regexp, RUBY_VERSION)
  end
  
  GEM_NAME_PATTERN = /.*?\-\d.*/
  
  def assert_gem(name, *versions)
    regexp = Regexp.union(*version_str(*versions).map{|version|
        Regexp.new("#{name}-#{version}")
      })
    gem_pathes = Gem.all_load_paths.map{|path| path.gsub(Gem.dir, '')}
    gem_names = gem_pathes.map do |path|
      parts = path.split('/')
      parts.reverse.detect{|part|GEM_NAME_PATTERN =~ part}
    end
    return true if gem_names.any?{|gem_name|regexp =~ gem_name}
    found_gem_names = gem_names.select{|gem_name|gem_name.include?(name)}
    if found_gem_names.empty?
      fail "#{name} #{versions.join(' or ')} not found"
    else
      fail "#{found_gem_names.join(', ')} found but #{versions.join(' or ')} of #{name} not found"
    end
  end
end

gemの名前を取得するのにGem.all_load_pathsを使ってパスを取得してごにょごにょしてますが、もっといい方法がありそうな予感がしてます。どなたかご存じだったら教えてほしいです〜