💎

Rubyの拡張ライブラリーにはインストールできるかのテストも書こう

に公開

RubyGemを作っている時、ピュアRubyのgemでGitコミットしたファイルがそのまま配信されるようなのはあまり問題にならないですが、拡張ライブラリーみたいにユーザーのインストール時にコマンドが走ったり、Raccみたいにリリース直前にファイルを生成してパッケージに含めたりするような物は、リリースしてからインストールできなかったり動作しないことに気付いたりするものです(済みません……)。

そういうのを事前に防ぐため、ユニットテストで「ちゃんとインストールできること」も確認するようにしましょう。

と、言いたいことはこれで全てなんですが、折角なので例も置いておきます。
ここではテスティングフレームワークにTest::Unitを使います。
ライブラリーは何でもいいんですが、RustでRubyの拡張を作る時にrb_sys gemは不要になっていたで作った拡張ライブラリーを使うことにします。

ビルドのテスト

まずは*.gemファイルをちゃんとビルドできることを確認します。
Raccで生成したファイルを入れ忘れていたり、*.gemspecファイルを書き間違えていたりすることがあるためです。

test/text_package.rb
require "test/unit"
require "tempfile"
require "shellwords"

class TestPackage < Test::Unit::TestCase
  def test_build
    Tempfile.create do |file|
      assert system("gem", "build", "my_ext.gemspec", "--output", file.to_path.shellescape, exception: true)
      assert file.size > 0
      assert_path_exist file.to_path
    end
  end
end

gem buildコマンドの--outputオプションで任意のファイルパスにパッケージを作れます。

インストールのテスト

次に拡張ライブラリーがちゃんとインストールできることをテストします。
メインはインストールの所なので準備はsetupメソッドに置いておきます。

sub_test_caseでグルーピングしておくと便利なことがあります。このgemでは無いですが、gemのインストール時にビルドオプションを渡せる場合なんかは、そのオプションの有無で別々にテストをしたり。

test/test_package.rb
@@ -1,6 +1,7 @@
 require "test/unit"
 require "tempfile"
 require "shellwords"
+require "tmpdir"
 
 class TestPackage < Test::Unit::TestCase
   def test_build
@@ -10,4 +11,27 @@ class TestPackage < Test::Unit::TestCase
       assert_path_exist file.to_path
     end
   end
+
+  sub_test_case "Building binary on installation" do
+    def setup
+      system "rake", "build", exception: true
+
+      @gemspec = Gem::Specification.load("my_ext.gemspec")
+    end
+
+    def test_install
+      Dir.mktmpdir do |dir|
+        # macOSではDir.mktmpdirのディレクトリー名とビルド時チェックのディレクトリー名が異なるので正規化
+        dir = File.realpath(dir)
+        system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{@gemspec.file_name.shellescape}", exception: true
+        assert_installed dir
+      end
+    end
+
+    private
+
+    def assert_installed(dir)
+      assert_path_exist File.join(dir, "gems/#{@gemspec.full_name}/lib/my_ext/#{@gemspec.name}.#{RbConfig::CONFIG["DLEXT"]}")
+    end
+  end
 end

gem installコマンドの--install-dirオプションでインストール先を変更できます。
Gem::Specificationが(当たり前ですが)インストールに関する情報を色々持っていて便利なのでインスタンス変数にして使い回しています(テスト中に変更されないから定数にしてもいいですね)。

軽く動作も確認できると安心でしょう。

test/test_package.rb
@@ -25,6 +25,10 @@ class TestPackage < Test::Unit::TestCase
         dir = File.realpath(dir)
         system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{@gemspec.file_name.shellescape}", exception: true
         assert_installed dir
+
+        libdir = File.join(dir, "gems", @gemspec.full_name, "lib")
+        output = `ruby -I #{libdir} -r my_ext -e 'print MyExt.hello("world")'`
+        assert_equal "Hello from Rust, world!", output
       end
     end

インストールオプションのテスト

今回のgemでは実際には動きませんが、こんな風にインストールオプション(ビルドオプション)も可能ならテストしておきましょう。

test/test_package.rb
@@ -32,6 +32,18 @@ class TestPackage < Test::Unit::TestCase
       end
     end
 
+    def test_install_with_option
+      Dir.mktmpdir do |dir|
+        dir = File.realpath(dir)
+        system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{@gemspec.file_name.shellescape}", "--", "--enable-foo", exception: true
+        assert_installed dir
+
+        libdir = File.join(dir, "gems", @gemspec.full_name, "lib")
+        output = `ruby -I #{libdir} -r my_ext -e 'print MyExt.hello_with_foo("world")'`
+        assert_equal "Hello from Rust with Foo, world!", output
+      end
+    end
+
     private
 
     def assert_installed(dir)

gem installコマンドに--enable-fooオプションを渡しています。

お終い

こんな風にしてテストしておくと、リリース後に慌てて次のリリースをすることもかなり減らせる筈です。
「パッケージにクレデンシャルが含まれていないこと」なんかもテストするといいかもですね。

この為のテストユーティリティとか、誰か作ってくれないかしら。

Discussion