MLton(+SML/NJ)のためのユニットテスト環境

前回(SML/NJでのUnitTest環境の構築)で構築した
ユニットテスト環境をSMLコンパイラである MLton で使えるようにします.
.cmファイル(SML/NJ用のmakefile)と.mlb(MLton用のmakefile)から同様に使えるようにするため,
SML/NJ用の環境も少し変更したので両方の環境構築方法を紹介します.


いずれの場合にも既に以下のようにチェックアウトが済んでいるとして説明します.

 svn checkout http://sml-ext.googlecode.com/svn/trunk/ sml-ext-read-only

MLton用の環境構築

チェックアウトしたファイルを一部修正します.

無理やりコメントアウト(^^;;

--- sources.mlb (revision 648)
+++ sources.mlb (working copy)
@@ -16,10 +16,11 @@
 util/sources.mlb
 timing/$(TIMING).mlb
 word/sources.mlb
-
+(*
 opt/cfsqp/cfsqp.mlb
 opt/knitro/knitro.mlb
-
+*)
 basis/list-ext-test.sml
 basis/real-ext-test.sml
 test/sml-ext-test.sml

今度は構文エラーを真面目に修正. (どーせショボイregexpなので使わないんですが…)

--- regexp/simple-regexp.sml    (revision 648)
+++ regexp/simple-regexp.sml    (working copy)
@@ -27,11 +27,10 @@
             case matches of
               NONE => false
             | SOME (tree,_) =>
-              (case MatchTree.nth(tree,0) of
-                 NONE => raise Impossible
-               | SOME {len,pos=_} => len = n)
+                let val {len,...} = MatchTree.nth(tree,0)
+                in len = n
+                end
           end
-
    end

あとは適切に.mlbファイルを記述してコンパイルすれば使うことが出来ます.
以下が .mlb ファイルのサンプルです

$ cat test.mlb
local
	$(SML_LIB)/basis/basis.mlb
	$(SML_EXT)/sources.mlb (* sml-extを指定 *)
	test.sml (* テストを書いてあるファイル *)
in
	structure Test (* テストする対象のstructure *)
end

これをコンパイルすれば実行ファイルが出来上がります. (多分もっと良い方法があるはずだけど…)

$ mlton -mlb-path-map /path/to/sml-ext-read-only/sml-ext.map \
        -mlb-path-var 'SML_EXT /path/to/sml-ext-read-only' test.mlb

テストの書き方は以下のようになります. 前回とほぼ同じですが, openするstructureの名前が変わっています.

$ cat test.sml
structure Test =
struct
  open UnitTests (* 前回はSmlExt.UnitTestsだった *)
  open Ops
  fun test () =
    $ ("Test" (* 全体に名前付ける *)
    , &[ % (fn()=> assertEqualEq (0, 0)) (* 0==0 *)
       , % (fn()=> assertEqualEq (0, 1)) (* 0==1 *)
       , % (fn()=> assertEqualEq (0, 2)) 
       , % (fn()=> assertEqualInt (1, 0))
	   (* ケースに名前を付ける *)
       , $ ("1==1", % (fn()=> assertEqualInt (1, 1)))
       , % (fn()=> assertEqualInt (1, 2))
       , % (fn()=> assertEqualReal (2.0, 0.0))
	   (* 特殊化された比較アサート *)
       , % (fn()=> assertEqualReal (2.1, 2.1))
       , % (fn()=> assertEqualReal (2.2, 2.19999))
       , % (fn()=> assertEqualReal (3.14, 3.14159))
       , % (fn()=> ? (6 = foldr op+ 0 [1,2,3]))
       , $ ("sum[1..10]=55",     (* ちょっと実際的?なケース *)
            % (fn()=> ? (55 = foldr op+ 0
                            (List.tabulate(10,fn i=> i)))))
      ])
end
val _ = UnitTests.runTest(Test.test()); (* 即テスト実行 *)

前回の方法では SmlExt がグローバルに公開され, 必要なものはその中にネストして定義されていました.
今回の方法では SmlExt は提供されず, sml-extライブラリが提供するstructureがそれぞれトップレベルに公開されます. *1
sml-ext.cm(とそこから読み込まれるファイル全部)に対応する.mlbファイルを書けば, 前回と同じように使えるハズ.

SML/NJ用の環境構築

  • vi sml-ext

そのままコンパイルするとエラーが出るため修正します(これは前回と同様)

 signature SmlExt'ORD_SET_EXT
 ↓
 signature SmlExt'SmlNjLib'ORD_SET_EXT
$ cd sml-ext-read-only/
# 前回
$ echo 'CM.stabilize true "sml-ext.cm";' | sml
# 今回
$ echo 'CM.stabilize true "sources.cm";' | sml
# 前回
$ echo "sml-ext.cm sml-ext.cm" >> $SMLNJ_HOME/lib/pathconfig
# 今回
$ echo "sml-ext sml-ext" >> $SMLNJ_HOME/lib/pathconfig
  • ファイルを移動. CMの性質上, 一段深いディレクトリに置く必要があります.
# 前回
$ mv sml-ext-read-only $SMLNJ_HOME/lib/sml-ext.cm
# 今回
$ mv sml-ext-read-only $SMLNJ_HOME/lib/sml-ext/sml-ext
  • 完了!


以下が.cmファイルのサンプルです. 上で説明しているように, structureの公開されるレベルが変わったので
それに合わせて明示的にUnitTests structureを指定します.

Library
  structure UnitTests (* 前回は SmlExt *)
  structure Test
is
  $/basis.cm
  $/sml-ext/sources.cm (* 前回は $/sml-ext.cm *)
  test.sml

cm2mlb

.cmファイルから.mlbを作成するツールはMLtonにより提供されています…

# なぜこんなところにある…w
$ cd /usr/share/doc/mlton/cm2mlb/ && make

が, コレが吐いたファイルは謎の番号が振られたstructureの羅列なのであんまりうれしくは無いですorz

まとめ

  • SML/NJとMLtonで同じユニットテストが実行できるようになりました!
  • cm2mlbは罠
  • CMのpathconfig設定は直感的じゃない><

*1:MLton用にデフォルトで用意されているファイル(sources.mlb)ではそうなっている