CPAN経由のText::MeCabインストールで失敗(解決編)

id:soybeens:20080530の続き。CPAN経由でText:MeCabがインストールにこける件。
今朝方日記にコメントを頂いたので、せっかくだから調べてみた。
なんだかだらだらとした書き方になってしまったので、お急ぎの方は結論だけ見てください。

なぜ失敗するのか

まず、失敗時のログを見ると、

t/node/03_clone................1/28
#   Failed test 'Deep clone node B OK'
#   at t/node/03_clone.t line 33.

t/node/03_clone.tの33行目で失敗している。
CPANがダウンロードしてきたパッケージは、CPANディレクトリ下のsource/authors/id/D/DM/DMAKI/Text-MeCab-0.20007.tar.gzにある。(tarボールの名前はバージョンによって違うかも)
これを手動で解凍して、該当ファイルを見ると、以下。

#!perl
use strict;
use utf8;
use Test::More (tests => 28);
use Encode;

BEGIN
{
    use_ok("Text::MeCab");
}

my $data = {
    taro => encode(Text::MeCab::ENCODING, "太郎は次郎が持っている本を花子に渡した。"),
    sumomo => encode(Text::MeCab::ENCODING, "すもももももももものうち。"),
};

my $mecab = Text::MeCab->new;

my $node_A_orig = $mecab->parse($data->{taro});
ok($node_A_orig, "Original node A OK");
my $node_A = $node_A_orig->dclone;

my $node_B_orig = $mecab->parse($data->{sumomo});
ok($node_B_orig, "Original node B OK");
my $node_B = $node_B_orig->dclone;

# XXX - better be at least 5 nodes after parsing (this may actually depend
# on the dictionary that you are using, but heck, if you are crazy enough
# to muck with the dictionary, then you know how to diagnose this test)

for(1..5) {
    ok($node_A, "Deep clone node A OK");
    ok($node_B, "Deep clone node B OK");
    isa_ok($node_A, "Text::MeCab::Node::Cloned", "Deep clone node A isa OK");
    isa_ok($node_B, "Text::MeCab::Node::Cloned", "Deep clone node B isa OK");

    if ($node_A->length != 0 || $node_B->length != 0) {
        isnt($node_A->surface, $node_B->surface, 
            sprintf(
                "Contents of cloned nodes must differ (A = %s, B = %s)",
                $node_A->surface,
                $node_B->surface,
            )
        );
    }

    $node_A = $node_A->next;
    $node_B = $node_B->next;
}

こんなテストやってたんだ(^^;
失敗しているのは、以下の行。

    ok($node_B, "Deep clone node B OK");

このok()って関数がなにやってるのかはわからんが、どうせ単体テスト用の判定ルーチンだろうと推測。
つまり、第一引数が非ゼロなら成功と見なすんでないか?
CppUnitとかでもそんなのあった気がするし。
て、ことは・・・エラーの原因は$node_Bがゼロ、つまりNULL(とゆー概念がperlにあるのかわからんが)ってことか。
要するに、インスタンス生成されていないってことだよね。
$node_Bの出所を探ると、すぐ上で生成している行が見つかる。

my $node_B = $node_B_orig->dclone;

つまり、$node_B_orig->dcloneの戻り値がNULLだ、と。
じゃあ、このdcloneという関数の実体はどこにあるのかというと、grepすると出てくる。
text-mecab-node.cの中。

TextMeCab_Node_Cloned *
TextMeCab_Node_dclone(node)
        TextMeCab_Node *node;
{
    TextMeCab_Node_Cloned *prev_node   = NULL;
    TextMeCab_Node_Cloned *cloned_node = NULL;
    TextMeCab_Node        *head        = NULL;
    TextMeCab_Node        *current     = NULL;
    TextMeCab_Node_Cloned *tmp         = NULL;
    TextMeCab_Node_Cloned_Meta *meta;

    /* XXX - We clone the entire node list, to make management easier */
    head = node;
    while (head->prev != NULL) {
        head = head->prev;
    }

    Newz(1234, meta, 1, TextMeCab_Node_Cloned_Meta);

    current = head;
    while(current != NULL) {
        tmp = TextMeCab_Node_clone_single_node(current);
        if (current == node) {
            cloned_node = tmp;
        }
        tmp->meta = meta;
        tmp->prev = prev_node;
        if (prev_node != NULL) {
            prev_node->next = tmp;
        } else {
            meta->first = tmp;
        }

        prev_node = tmp;
        current = current->next;
    }

    meta->refcnt++;
    return cloned_node;
}

C言語の関数だけど、同名の関数だから、多分これが本体だろう、と。
perlからどうにかしてこのCの関数を呼び出してるわけだ。
で、この関数がNULLを返のは、head=NULLの場合しか考えられない。
headってのは形態素解析した要素のチェインの先頭なわけで、そいつがNULLってことは・・・。
解析に失敗してんでないの?(^^;


もう一度、テストのコードに戻る。

my $data = {
    taro => encode(Text::MeCab::ENCODING, "太郎は次郎が持っている本を花子に渡した。"),
    sumomo => encode(Text::MeCab::ENCODING, "すもももももももものうち。"),
};

my $mecab = Text::MeCab->new;

my $node_A_orig = $mecab->parse($data->{taro});
ok($node_A_orig, "Original node A OK");
my $node_A = $node_A_orig->dclone;

my $node_B_orig = $mecab->parse($data->{sumomo});
ok($node_B_orig, "Original node B OK");
my $node_B = $node_B_orig->dclone;

上記のうち、$node_Aはちゃんとインスタンス生成されているのに、$node_Bはインスタンス生成されない。
その違いは?・・・$node_Bの元になった文字列って、ひらがなだけだよねぇ。
つまり、漢字が混じるとうまく解析できるけど、ひらがなだけだと解析できない。・・・原因、文字コードくさくない?
上記コードで文字コード指定してるのはText::MeCab::ENCODINGっていう変数。これ、何だろう・・・。
はたと気付く。
そういや、テスト始める前に辞書の文字コードを聞かれた。アレでないのか?

Encoding of your mecab dictionary? (shift_jis, euc-jp, utf-8) [utf-8] 

何も考えずにそのままEnter押したんだけど、辞書がUTF-8じゃないんじゃないの?


Text::MeCabCPAN経由でインストールしているが、MeCab本体はaptitudeを使ってインストールした。
デフォルトの文字コードが何になってるのか、よくわからん。
それなら、当たってくだけてみよう、と、とりあえずEUCを指定してみた。

Encoding of your mecab dictionary? (shift_jis, euc-jp, utf-8) [utf-8] euc-jp
Using euc-jp as your dictionary encoding
Detected the following mecab information:
   version: 0.93
   cflags: -DMECAB_MAJOR_VERSION=0 -DMECAB_MINOR_VERSION=93 -I src
   libs: -L/usr/lib -lmecab -lstdc++
   include: /usr/include
reading /usr/include/mecab.h to find all constants
Checking if your kit is complete...
Looks good
Writing Makefile for Text::MeCab
cp lib/Text/MeCab/Dict.pm blib/lib/Text/MeCab/Dict.pm
cp lib/Text/MeCab/Node.pod blib/lib/Text/MeCab/Node.pod
cp lib/Text/MeCab.pm blib/lib/Text/MeCab.pm
cp lib/Text/MeCab.xs blib/lib/Text/MeCab.xs
/usr/bin/perl /usr/share/perl/5.8/ExtUtils/xsubpp  -typemap /usr/share/perl/5.8/ExtUtils/typemap -typemap typemap  MeCab.xs > MeCab.xsc && mv MeCab.xsc MeCab.c
cc -c   -DMECAB_MAJOR_VERSION=0 -DMECAB_MINOR_VERSION=93 -I src -O2   -DVERSION=\"0.20007\" -DXS_VERSION=\"0.20007\" -fPIC "-I/usr/lib/perl/5.8/CORE"  -DTEXT_MECAB_ENCODING='"euc-jp"' -DTEXT_MECAB_CONFIG='"/usr/bin/mecab-config"' MeCab.c
In file included from MeCab.xs:7:
text-mecab.h:124:30: warning: no newline at end of file
cc -c   -DMECAB_MAJOR_VERSION=0 -DMECAB_MINOR_VERSION=93 -I src -O2   -DVERSION=\"0.20007\" -DXS_VERSION=\"0.20007\" -fPIC "-I/usr/lib/perl/5.8/CORE"  -DTEXT_MECAB_ENCODING='"euc-jp"' -DTEXT_MECAB_CONFIG='"/usr/bin/mecab-config"' text-mecab-clone.c
In file included from text-mecab-clone.c:7:
text-mecab.h:124:30: warning: no newline at end of file
cc -c   -DMECAB_MAJOR_VERSION=0 -DMECAB_MINOR_VERSION=93 -I src -O2   -DVERSION=\"0.20007\" -DXS_VERSION=\"0.20007\" -fPIC "-I/usr/lib/perl/5.8/CORE"  -DTEXT_MECAB_ENCODING='"euc-jp"' -DTEXT_MECAB_CONFIG='"/usr/bin/mecab-config"' text-mecab-node.c
In file included from text-mecab-node.c:7:
text-mecab.h:124:30: warning: no newline at end of file
cc -c   -DMECAB_MAJOR_VERSION=0 -DMECAB_MINOR_VERSION=93 -I src -O2   -DVERSION=\"0.20007\" -DXS_VERSION=\"0.20007\" -fPIC "-I/usr/lib/perl/5.8/CORE"  -DTEXT_MECAB_ENCODING='"euc-jp"' -DTEXT_MECAB_CONFIG='"/usr/bin/mecab-config"' text-mecab.c
In file included from text-mecab.c:7:
text-mecab.h:124:30: warning: no newline at end of file
text-mecab.c:116:30: warning: no newline at end of file
Running Mkbootstrap for Text::MeCab ()
chmod 644 MeCab.bs
rm -f blib/arch/auto/Text/MeCab/MeCab.so
LD_RUN_PATH="/usr/lib" cc  -shared -L/usr/local/lib MeCab.o text-mecab-clone.o text-mecab-node.o text-mecab.o  -o blib/arch/auto/Text/MeCab/MeCab.so        \
           -lmecab      \

chmod 755 blib/arch/auto/Text/MeCab/MeCab.so
cp MeCab.bs blib/arch/auto/Text/MeCab/MeCab.bs
chmod 644 blib/arch/auto/Text/MeCab/MeCab.bs
Manifying blib/man3/Text::MeCab::Dict.3pm
lib/Text/MeCab/Dict.pm:166: Unknown command paragraph "=encoding UTF-8"
Manifying blib/man3/Text::MeCab::Node.3pm
Manifying blib/man3/Text::MeCab.3pm
  DMAKI/Text-MeCab-0.20007.tar.gz
  make -- OK
Running make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t t/*/*.t
t/01-sanity....................ok
t/99-pod-coverage..............skipped: Enable TEST_POD environment variable to test POD
t/99-pod.......................skipped: Enable TEST_POD environment variable to test POD
t/node/01_load.................ok
t/node/02_api..................ok
t/node/03_clone................ok
t/node/04_clone_free...........ok
t/node/05_format...............ok
t/regression/01_tomi_args......skipped: SWIG MeCab not available
t/tagger/01_load...............ok
t/tagger/02_api................ok
t/tagger/03_basic..............ok
All tests successful.
Files=12, Tests=2280, 20 wallclock secs ( 6.10 usr  0.41 sys + 11.53 cusr  0.89 csys = 18.93 CPU)
Result: PASS
  DMAKI/Text-MeCab-0.20007.tar.gz
  make test -- OK

あっさり通ったorz
ぐぐってもよくわからなかったのに、腹くくってコード見てみたら、15分で解決かよ・・・。

結論

id:soybeens:20080530と同じ症状が出たら、辞書の文字コード指定が間違っていると思われる。
debianでapt使ってMeCab本体をインストールした場合は、デフォルト辞書の文字コードeuc-jpになってると思われるので、テスト時はeuc-jpを指定すること。