Feature #2366

private constant

Added by Yusuke Endoh over 5 years ago. Updated about 4 years ago.

[ruby-dev:39685]
Status:Closed
Priority:Normal
Assignee:Yusuke Endoh

Description

=begin
遠藤です。

今の Ruby には、クラスが公開 API かどうかを伝える手段がドキュメント
しかありません。そのため、ERB::Compiler など、ライブラリの中の公開
でない (と思われる) inner class を外から自由に参照できてしまいます。

これを防ぐためには、匿名クラスを用いて定義すれば大分隠蔽できますが、
記述が相当煩雑になってしまいます。また、そのようにしてしまうと、
「非公開というのは承知の上で敢えて使いたい」という要求に答えにくく
なります。

そこで、定数に public/private の属性を指定できるようにするのはどう
でしょうか。

module SomeMod
class PublicInnerCls
end

 class PrivateInnerCls
 end

 # PrivateInnerCls を private にする
 private_constant :PrivateInnerCls

end

# public な定数は従来どおり参照できる
p SomeMod::PublicInnerCls #=> SomeMod::PublicInnerCls

# private な定数を外から参照しようとすると例外
p SomeMod::PrivateInnerCls #=> private constant (RuntimeError)

# 同じスコープからは参照できる (望むなら自分の足を撃てる)
p SomeMod.module_eval { PrivateInnerCls } #=> SomeMod::PrivateInnerCls
p(module SomeMod; PrivateInnerCls; end) #=> SomeMod::PrivateInnerCls

要するに、メソッドの public/private と同じ感じです。

細かい仕様は詰めていませんが、proof of concept のパッチを付けます。
1 定数ごとに 2 要素の配列を作ってしまうので、ちゃんとした実装は必要
だと思います。

どんなものでしょうか。

ちなみにこの提案のきっかけは です。

diff --git a/variable.c b/variable.c
index 779a8e8..db3a81c 100644
--- a/variable.c
+++ b/variable.c
@@ -1525,6 +1525,33 @@ rb_autoload_p(VALUE mod, ID id)
return load && (file = load->nd_lit) ? file : Qnil;
}

+void
+rb_change_const_visibility(VALUE klass, ID id, VALUE ex)
+{
+ VALUE value, tmp;
+ int mod_retry = 0;
+
+ tmp = klass;
+ while (RTEST(tmp)) {
+ VALUE am = 0;
+ while (RCLASS_IV_TBL(tmp) && st_lookup(RCLASS_IV_TBL(tmp), (st_data_t)id, &value)) {
+ if (value == Qundef) {
+ if (am == tmp) break;
+ am = tmp;
+ rb_autoload_load(tmp, id);
+ continue;
+ }
+ if (tmp == rb_cObject && klass != rb_cObject) {
+ rb_warn("toplevel constant %s referenced by %s::%s",
+ rb_id2name(id), rb_class2name(klass), rb_id2name(id));
+ }
+ RARRAY_PTR(value)[1] = ex;
+ return;
+ }
+ tmp = RCLASS_SUPER(tmp);
+ }
+}
+
static VALUE
rb_const_get_0(VALUE klass, ID id, int exclude, int recurse)
{
@@ -1546,7 +1573,10 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse)
rb_warn("toplevel constant %s referenced by %s::%s",
rb_id2name(id), rb_class2name(klass), rb_id2name(id));
}
- return value;
+ if (!RARRAY_PTR(value)[1]) {
+ rb_raise(rb_eRuntimeError, "private constant");
+ }
+ return RARRAY_PTR(value)[0];
}
if (!recurse && klass != rb_cObject) break;
tmp = RCLASS_SUPER(tmp);
@@ -1798,11 +1828,13 @@ mod_av_set(VALUE klass, ID id, VALUE val, int isconst)
void
rb_const_set(VALUE klass, ID id, VALUE val)
{
+ VALUE ary;
if (NIL_P(klass)) {
rb_raise(rb_eTypeError, "no class/module to define constant %s",
rb_id2name(id));
}
- mod_av_set(klass, id, val, TRUE);
+ ary = rb_ary_new3(2, val, Qtrue);
+ mod_av_set(klass, id, ary, TRUE);
}

void
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 0660c7d..e3e789e 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -1141,7 +1141,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq,
return 1;
}
else {
- return val;
+ return RARRAY_PTR(val)[0];
}
}
}
diff --git a/vm_method.c b/vm_method.c
index 557583f..40b125a 100644
--- a/vm_method.c
+++ b/vm_method.c
@@ -1038,6 +1038,30 @@ rb_mod_private_method(int argc, VALUE *argv, VALUE obj)
return obj;
}

+static void
+set_const_visibility(VALUE self, int argc, VALUE argv, VALUE ex)
+{
+ int i;
+ extern void rb_change_const_visibility(VALUE klass, ID id, VALUE ex);
+ secure_visibility(self);
+ for (i = 0; i < argc; i++) {
+ rb_change_const_visibility(self, rb_to_id(argv[i]), ex);
+ }
+ rb_clear_cache_by_class(self);
+}
+
+static VALUE
+rb_mod_public_constant(int argc, VALUE *argv, VALUE obj)
+{
+ set_const_visibility(obj, argc, argv, Qtrue);
+}
+
+static VALUE
+rb_mod_private_constant(int argc, VALUE *argv, VALUE obj)
+{
+ set_const_visibility(obj, argc, argv, Qfalse);
+}
+
/

* call-seq:
* public
@@ -1250,6 +1274,8 @@ Init_eval_method(void)
rb_define_method(rb_cModule, "protected_method_defined?", rb_mod_protected_method_defined, 1);
rb_define_method(rb_cModule, "public_class_method", rb_mod_public_method, -1);
rb_define_method(rb_cModule, "private_class_method", rb_mod_private_method, -1);
+ rb_define_method(rb_cModule, "public_constant", rb_mod_public_constant, -1);
+ rb_define_method(rb_cModule, "private_constant", rb_mod_private_constant, -1);

  rb_define_singleton_method(rb_vm_top_self(), "public", top_public, -1);
  rb_define_singleton_method(rb_vm_top_self(), "private", top_private, -1);

--
Yusuke Endoh mame@tsg.ne.jp
=end

const_tbl.patch Magnifier (10 KB) Yusuke Endoh, 11/15/2009 03:50 AM

const_entry.patch Magnifier (10.1 KB) Yusuke Endoh, 11/15/2009 03:50 AM

private_constant.patch Magnifier (3.3 KB) Yusuke Endoh, 11/15/2009 03:50 AM

History

#1 Updated by Yukihiro Matsumoto over 5 years ago

=begin
まつもと ゆきひろです

In message "Re: [Feature #2366] private constant"
on Sat, 14 Nov 2009 14:30:32 +0900, Yusuke Endoh redmine@ruby-lang.org writes:

|今の Ruby には、クラスが公開 API かどうかを伝える手段がドキュメント
|しかありません。そのため、ERB::Compiler など、ライブラリの中の公開
|でない (と思われる) inner class を外から自由に参照できてしまいます。
|
|これを防ぐためには、匿名クラスを用いて定義すれば大分隠蔽できますが、
|記述が相当煩雑になってしまいます。また、そのようにしてしまうと、
|「非公開というのは承知の上で敢えて使いたい」という要求に答えにくく
|なります。
|
|そこで、定数に public/private の属性を指定できるようにするのはどう
|でしょうか。

なるほど、良いアイディアだと思います。採用するタイミングは
Yuguiさんに訊いてみないといけませんが、いずれ採用したいと思い
ます。Yuguiさんはどう思いますか?

=end

#2 Updated by Yusuke Endoh over 5 years ago

=begin
遠藤です。

なるほど、良いアイディアだと思います。採用するタイミングは
Yuguiさんに訊いてみないといけませんが、いずれ採用したいと思い
ます。

ありがとうございます。

ささださんと話して、もうちょっとちゃんとした実装を作りました。
結構巨大な変更になったので、3 段階のパッチに分けました。

  1. const_tbl.patch:
    クラスの持つ定数やクラス変数やその他をごちゃまぜに管理している
    RCLASS_IV_TBL から、定数だけを管理する RCLASS_CONST_TBL を切り
    出す。

  2. const_entry.patch
    RCLASS_CONST_TBL のエントリが定数の直接値を指していたところを、
    rb_const_entry_t をはさんで間接的に指すようにする。
    rb_const_entry_t には visibility のフラグを持たせる。
    rb_const_entry_t は malloc で管理される。

  3. private_constant.patch
    Module#public_constant と private_constant を定義し、private
    constant の参照で例外を投げる。

make test-all でエラーが増えることはないようです。

Yuguiさんはどう思いますか?

どうでしょうか。

API を練る必要がありそう (Module#constants に引数持たせるかとか
Module#private_constants を作るかとか) なので、無理に 1.9.2 に
入れなくてもいいと思いますが、定数テーブルの切り出しが地味に大変
だった (1 のパッチ) ので、これだけ入れておくと後で楽そうです。

しかし、ここまで書いてから気がつきましたが、もし rb_classext_t や
RCLASS_IV_TBL が公開 API だったら、このパッチはバイナリ互換性を
壊すことになりそうです。関数レベルでの互換性は変わらない (はず)
ですが。

--
Yusuke Endoh mame@tsg.ne.jp
=end

#3 Updated by ujihisa . over 5 years ago

  • Status changed from Open to Assigned

=begin

=end

#4 Updated by Yusuke Endoh over 5 years ago

=begin
遠藤です。

今の Ruby には、クラスが公開 API かどうかを伝える手段がドキュメント
しかありません。そのため、ERB::Compiler など、ライブラリの中の公開
でない (と思われる) inner class を外から自由に参照できてしまいます。

なるほど、良いアイディアだと思います。採用するタイミングは
Yuguiさんに訊いてみないといけませんが、いずれ採用したいと思い
ます。Yuguiさんはどう思いますか?

しかし、ここまで書いてから気がつきましたが、もし rb_classext_t や
RCLASS_IV_TBL が公開 API だったら、このパッチはバイナリ互換性を
壊すことになりそうです。関数レベルでの互換性は変わらない (はず)
ですが。

Yugui さんの返事はメールでは来ていませんが、IRC で

  • 1.9.2 では rb_classext_t の定義に /** internal */ と付ける (処置済み)
  • 1.9.3 以降でこの変更を入れる

という判断をもらっています。

ですが、最近 rubyspec に目を通していて、この機能は早く入れた方がいいと
思いました。

rubyspec では「private method は基本的に spec に書かない (#initialize
など一部例外はある)」という convension がありつつも、:nodoc: の付いた
CGI::Html3 みたいなクラスの spec を書いていて、そのせいで 1.9 でエラー
が大量に発生するようになっています。
これは、private constant さえあれば発生しなかった不幸だと思います。

従って、非公開クラスであることを明示する private constant を入れる事は
わりと急務ではないかと思います。さらに、rubyspec をパスすることを標榜
している ruby 1.9.2 のリリースを早めることに繋がるんじゃないかなあとも
期待しています。
非互換については、無視できるレベルだと思います。

というわけで、Yugui さんどうでしょうか。

--
Yusuke Endoh mame@tsg.ne.jp

=end

#5 Updated by Yusuke Endoh over 5 years ago

  • Assignee changed from Yukihiro Matsumoto to Yuki Sonoda

=begin

=end

#6 Updated by Yusuke Endoh over 5 years ago

=begin
遠藤です。

2010年1月29日20:30 Masatoshi SEKI :

今の Ruby には、クラスが公開 API かどうかを伝える手段がドキュメント
しかありません。そのため、ERB::Compiler など、ライブラリの中の公開
でない (と思われる) inner class を外から自由に参照できてしまいます。

すみません。教えて下さい。

非公開クラスというのは、rubyspecよ、テストコードを書かないで!という
宣言をするものですか?

それとも、アプリケーションが使ったり動的に定義をかえてはいけません、
という宣言をするものですか?

両方のつもりです。

たとえば、ERB::Compilerでしたら、specを書いてほしいとは思わないけど
アプリケーションで定義を書き換えて、ERBの挙動を変えるのは止めさせたい
とまでは思いません。

元の提案 に書きましたが、「非公開というのは承知の上で
敢えて使いたい」という要求を封じるものではありません。使うのに一手間
かかるようになるだけです。一手間かけることで、自分が自分の足を撃とうと
していることを自覚してもらおうという狙いです。

もしERBの実装が変わってしまってその書き換えをするコードが動かなくなって
しまうのは、仕様が変わった、と納得してもらっても良いような気がします。

納得してくれる人はいいんですけどね。rubyspec の様子を見ると、そういう
牧歌的な期待は難しい気がしています。

普通に見えるものは何でも spec にされてしまう例として、以下のような spec
があります。(private constant には関係ないですが……)

http://github.com/rubyspec/rubyspec/blob/master/library/erb/src_spec.rb

つまり、ERB#src の返り値を固定文字列で spec にしているという。1.9 で失敗
してるんですが、どう対処したものでしょう。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#7 Updated by Yui NARUSE over 5 years ago

=begin
成瀬です。

(2010/01/30 3:57), Masatoshi SEKI wrote:

元の提案 に書きましたが、「非公開というのは承知の上で
敢えて使いたい」という要求を封じるものではありません。使うのに一手間
かかるようになるだけです。一手間かけることで、自分が自分の足を撃とうと
していることを自覚してもらおうという狙いです。

うーん。そうするとそう宣言しなかったものは、どうなるんでしょう。

「変えないって言ったのに変更しやがって!」と怒られるとか?

「変えない」と言った覚えはないわけですが、
公開APIである以上安易な仕様変更は慎む事が期待されます。
そもそも、Rails 以降 Ruby でも仕様変更は控えることが期待されつつあります。

もしERBの実装が変わってしまってその書き換えをするコードが動かなくなって
しまうのは、仕様が変わった、と納得してもらっても良いような気がします。

納得してくれる人はいいんですけどね。rubyspec の様子を見ると、そういう
牧歌的な期待は難しい気がしています。

えと、rubyspecというのはそういう変化をみつけてくれるセンサーのような
ものではなく、なにかを規定するものなんでしょうか。

"spec" ですし。
そもそも、RubySpec は誰のためのものかって、MRI のためじゃないんですよ。
JRuby や Rubinius、MacRuby、あと IronRuby のためのものです。
彼らが実装すべき「Ruby」の言語仕様を規定するのが RubySpec です。

テストによって変化を見つけたら、変更したと宣言すればよいのではないの
ですか?

rubyspecに書かれたものは、修正することができなくなるということでしょうか?

RubySpec に書かれたもの――というか、書かれうる物は公開 API ですので、
それを修正する事は仕様変更、ひいては「Ruby」言語仕様の変更を意味し、
Ruby 互換実装を作っている人々全てに追従を要求することになります。
たとえ誰も使っていない API でも。

それでも仕様変更であると宣言するならばもちろん変更は可能です。

普通に見えるものは何でも spec にされてしまう例として、以下のような spec
があります。(private constant には関係ないですが……)

http://github.com/rubyspec/rubyspec/blob/master/library/erb/src_spec.rb

つまり、ERB#src の返り値を固定文字列で spec にしているという。1.9 で失敗
してるんですが、どう対処したものでしょう。

そのセンスがいいかどうかはおいといて‥

センスの問題じゃないんですよ。
RubySpec の作り手達にって、彼らのユーザは悪名高き Rails なわけで、
ERB#src だろうがなんだろうが容赦なく使ってきます。
ですから、公開 API ならばテストするのは当然です。

ERB#srcの結果の文字列に興味がある人にとっては、違うものを返してるの
だから、仕様変更しやがったんですよね。

MatzRuby は仕様変更の場合アプリケーション側に追従を要求する文化ですが、
現状互換実装は立場が弱いので。

あるバージョンのERB#srcとまた別のバージョンのERB#srcの挙動は違うので
あれば、全てのバリエーションについて期待する結果を書かせてあげても
よいのではないですかねえ。

テストを書いて終わりじゃないという事は以上でご理解いただけた事かと思います。

--
NARUSE, Yui naruse@airemix.jp

=end

#8 Updated by Yuki Sonoda over 5 years ago

=begin
On 1/28/10 8:55 PM, Yusuke ENDOH wrote:

Yugui さんの返事はメールでは来ていませんが、IRC で

  • 1.9.2 では rb_classext_t の定義に /** internal */ と付ける (処置済み)
  • 1.9.3 以降でこの変更を入れる

という判断をもらっています。

ですが、最近 rubyspec に目を通していて、この機能は早く入れた方がいいと
思いました。

はい。まず、rb_classext_tは公開APIに含まれないという認識を確認しました。
そのため/** @internal */を付けました。公開APIでなく、ユーザー側で
rb_classext_tを確保する類のマクロもないのでメンバーを足すのは問題ありま
せん。

言語の拡張なのでちょっと慎重に行きたいと思って1.9.2では見送ることを考え
ました。しかし、もう少し広く-coreにも話を振って異論がないようならば1.9.2
に入れても差し支えないです。

--
Yugui yugui@yugui.jp
http://yugui.jp
私は私をDumpする

=end

#9 Updated by Yusuke Endoh over 5 years ago

=begin
遠藤です。

2010年1月30日14:34 Yugui (Yuki Sonoda) yugui@yugui.jp:

On 1/28/10 8:55 PM, Yusuke ENDOH wrote:

Yugui さんの返事はメールでは来ていませんが、IRC で

  • 1.9.2 では rb_classext_t の定義に /** internal */ と付ける (処置済み)
  • 1.9.3 以降でこの変更を入れる

という判断をもらっています。

ですが、最近 rubyspec に目を通していて、この機能は早く入れた方がいいと
思いました。

はい。まず、rb_classext_tは公開APIに含まれないという認識を確認しました。
そのため/** @internal */を付けました。公開APIでなく、ユーザー側で
rb_classext_tを確保する類のマクロもないのでメンバーを足すのは問題ありま
せん。

言語の拡張なのでちょっと慎重に行きたいと思って1.9.2では見送ることを考え
ました。しかし、もう少し広く-coreにも話を振って異論がないようならば1.9.2
に入れても差し支えないです。

ありがとうございます。ruby-core に投げてみたいと思います。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#10 Updated by Yusuke Endoh over 5 years ago

=begin
遠藤です。

2010年1月30日7:20 Masatoshi SEKI :

普通に見えるものは何でも spec にされてしまう例として、以下のような spec
があります。(private constant には関係ないですが……)

http://github.com/rubyspec/rubyspec/blob/master/library/erb/src_spec.rb

つまり、ERB#src の返り値を固定文字列で spec にしているという。1.9 で失敗
してるんですが、どう対処したものでしょう。

いや、使ってほしいAPIだしテストしてくださってかまわないのですが、

純粋に公開 API なんですね。想定用途は、コンパイル結果のキャッシュ?
だとしたら Proc とか返した方がよかった気もします (ERB 開発当時の
Ruby の状況を知らないですが) 。

いずれにせよ公開 API なら、spec に書くのがよさそうです。それなら、

単純に文字列比較で検査するから問題なんですよね。たぶん。

そういう問題だと思います。ERB#src は「こういう条件下でこういう
文字列を返す関数」という仕様ではない、ということで、

こういうテストにした方がいいよ、っていうのを教えてあげたら
よいのかな。

それがいいと思います。正確には「ERB#src はどういう仕様か」を教えて
あげるといいと思います。

推察するに、「実行可能な Ruby コードの文字列を返す」というのが仕様
でしょうか。だとすれば、文字列の中身を見るのではなく、ripper で
パースが通ることをみるとか、実際に eval してみるのが正しい spec の
テストと言えそうです。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#11 Updated by Yusuke Endoh over 5 years ago

=begin
遠藤です。

2010年1月30日21:39 Masatoshi SEKI :

なぜ現在のRubyの状況ではProcの方がよいのですか?
私は1.9.xでそう変更すべきでしょうか?

ERBはeRubyのスクリプトからRubyのスクリプトへの変換を担当します。
ERB#srcこそが本来やりたかったものです。

作っていると曖昧になりがちですが、「ERB の言語を実行する」が目的で
あり、「ERB の言語に相当する Rubyコードを合成する」はあくまで手段
ではないでしょうか。
この目的を達成するために、手段の詳細を下手にさらけ出すと、将来の
改良やバグ修正で仕様を変更することになるリスクが高いと思ったため
Proc の方がいいといいました。

でも、Proc にすると

変換結果をファイルを書く人もいるようですが、Procを作るなり
好きに使えばよいと思います。

Proc は Marshal できないからファイルに書き出すというユースケースが
満たせなくなりますね。すみません。

推察するに、「実行可能な Ruby コードの文字列を返す」というのが仕様
でしょうか。

いえ、実行可能なRubyコードかどうかは、元の文字列によるので
わからないと思います。

もうちょっと言うと「ERB 言語として正しい記述が入力されたら、実行
可能な Ruby コードの文字列を返す」ですかね。
「ERB 言語として正しい記述」ってなんだ、ということになりますが。

あー、でもrubyspecって本当はなにをテストしたいんだろう。
テストじゃないんでしたね。仕様か。
仕様っていうのはいつもテストコードに書けるんですかねえ。

書けないですね。極端な例では、String#dump は「必ず停止する Ruby
コードを出力する」という仕様があると思われますが、停止性判定は
決定不能なので判定できないです。

というかテストコードにあるケースだけをパスするのが仕様なのかなあ。

rubyspec は正確には、「Ruby 処理系が満たすべき仕様」と「仕様に
沿っていることをざっとチェックする conformance test」の組です。
rubyspec は実際には

describe "Array#zip" do
it "returns an array of arrays containing corresponding elements of
each array" do
[1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]).should ==
[[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
end
end

みたいに書かれています。この中で「仕様」なのは、

Array#zip returns an array of arrays containing corresponding elements
of each array

の自然言語の部分です。ブロック中にあるのが「conformance test」
です。

はたして、本当にあのときとバージョンとこのときのバージョンが
返す文字列が違うにも関わらず、そういうことを記述しなくても
よいのでしょうか。

なんかわかってきました。それは多分設計仕様とか詳細仕様とか言われる
ものですね。

rubyspec は「MRI と添付ライブラリの詳細仕様」ではなく、「Ruby 言語と
標準ライブラリの外部仕様」を書くことを目的としています。
「これを満たしていれば Ruby を名乗っていいよ」という基準なので、
実装方法など細かいことを規定しすぎると、ある OS では Ruby を名乗る
プログラムを作れないとか、最適化したら Ruby を名乗れなくなったとか、
問題が起こりますので、ある程度自由度のある仕様にする必要があります。

# といっても、以上は私の理解なので、間違っているかもしれません。
# rubyspec は「Ruby のバージョン間の違いを網羅する」というサブの
# 目的も掲げていた気がするので、そっちの意味では書いた方がいいのかも。

その文字列の変化によって、おかしなユーザのコードが奇妙な
挙動を示すこともあると思いますが、そういうのを調べる手がかり
なりそうに思うんだけどなあ。

そういうセンサーではないなら、ま、いっか。

それはそれで価値があるものですが、rubyspec ではなく test/ 以下に置く
といいと思います。

時間と元気があるときに、evalするようなテストケースを提案してみます。
英語で意図を説明するのは私には無理なので、コード送ります。たぶん。

多くの方にご迷惑おかけして申し訳ありませんでした。

いえ、全く迷惑ではないです。
ERB の設計意図がいろいろわかったので参考になりました。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#12 Updated by Yusuke Endoh over 5 years ago

=begin
咳さん
遠藤です。

2010年1月30日22:45 Yusuke ENDOH mame@tsg.ne.jp:

rubyspec は「MRI と添付ライブラリの詳細仕様」ではなく、「Ruby 言語と
標準ライブラリの外部仕様」を書くことを目的としています。

知ったかでいろいろいいましたが、rubyspec の人を突っついてみたところ、
という回答がきました。

サードパーティの Ruby コードが「Ruby 実装」(たぶん標準添付ライブラリも
含む) に期待することを書き出したい。そのために MRI のテストから (?) 、
ホワイトボックステスト、回帰テスト、ストレステストなどを除いたテスト
(要するに conformance test として使えるもの) を作る。
らしいです。

ご参考まで。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#13 Updated by Yusuke Endoh about 5 years ago

=begin
咳さん
遠藤です。

2010年1月30日21:39 Masatoshi SEKI :

時間と元気があるときに、evalするようなテストケースを提案してみます。
英語で意図を説明するのは私には無理なので、コード送ります。たぶん。

eval するような spec に書きなおして見ました。

私は rubyspec のコミット権を持っているので、咳さんさえよければすぐに
コミットしてしまいますが、よろしいでしょうか。

diff --git a/library/erb/src_spec.rb b/library/erb/src_spec.rb
index 8ea89b5..7b11041 100644
--- a/library/erb/src_spec.rb
+++ b/library/erb/src_spec.rb
@@ -3,51 +3,30 @@ require File.dirname(FILE) + '/../../spec_helper'

describe "ERB#src" do

  • ruby_version_is "" ... "1.8.7" do
  • it "returns the compiled ruby code" do
  • input = <<'END'
  • it "returns the compiled ruby code evaluated to a string" do
  • # note that what concrete code is emitted is not guaranteed. +
  • input = <<'END' <% for item in list %> <%= item %> <% end %> END
  • expected = <<'END' -_erbout = ''; _erbout.concat "\n"
  • for item in list ; erbout.concat "\n" -_erbout.concat ""; _erbout.concat(( item ).tos); _erbout.concat "\n"
  • end ; erbout.concat "\n" -_erbout.concat "\n" -erbout -END -
  • expected.chomp!
  • ERB.new(input).src.should == expected
  • end
  • end

  • ruby_version_is "1.8.7" do

  • it "returns the compiled ruby code" do

  •  input = <<'END'
    
  • expected = <<'END'

    -<% for item in list %>
    -<%= item %>
    -<% end %>
    -
    -END

  •  expected = <<EOS
    

    -erbout = ''; _erbout.concat "\n"
    -; for item in list ; _erbout.concat "\n"
    -; _erbout.concat(( item ).to
    s); _erbout.concat "\n"
    -; end ; _erbout.concat "\n\n"
    +1

-; _erbout
-EOS
+2

  • expected.chomp!
  • ERB.new(input).src.should == expected
  • end +3 + + +END
  • list = [1, 2, 3]
  • eval(ERB.new(input).src).should == expected end

end

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#14 Updated by Yusuke Endoh about 5 years ago

=begin
咳さん
遠藤です。

2010年2月10日18:22 Masatoshi SEKI :

eval するような spec に書きなおして見ました。

私は rubyspec のコミット権を持っているので、咳さんさえよければすぐに
コミットしてしまいますが、よろしいでしょうか。

ありがとうございます。よいと思います。

期待値はERB#resultの値(%w[AAA BBB CCC]を使ったもの)にあわせるのも
よいかと思いました。

ありがとうございます。そのようにしてコミットしました。

http://github.com/rubyspec/rubyspec/blob/master/library/erb/src_spec.rb

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#15 Updated by Yuki Sonoda about 5 years ago

  • Assignee changed from Yuki Sonoda to Yusuke Endoh

=begin

=end

#16 Updated by Yusuke Endoh over 4 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

=begin
This issue was solved with changeset r29603.
Yusuke, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.

=end

Also available in: Atom PDF