From: Takao Kouji Date: 2008-07-19T09:24:09+09:00 Subject: [ruby-dev:35588] Re: [Ruby 1.8 - Bug #212] Issues with Readline in Mac OS X 高尾宏治です。 On 2008/07/17, at 9:16, Takao Kouji wrote: > On 2008/07/17, at 5:38, Keita Yamaguchi wrote: >> 2008/7/17 Takao Kouji : >>> どうも、ヒストリのインデックスの開始が、 >>> * GNU Readline は 1。(history_base と同じ) >>> * libedit は 0。 >>> という違いがあるようです。 >>> このため、remove_historyなども同様に修正する必要がありました。 >>> これは以下のコードで確認しました。 >> >> このコードを Ubuntu 8.04 の libedit で実行してみたのですが、 >> GNU ReadLine の結果と同一になりました。 > (snip) >> libedit のバージョンによって挙動が変わることになるのではないかと思います。 >> ひょっとして Mac OSX のバージョンによっては古い libedit が入っていたりして、 >> 高尾さんのパッチによって影響を受けてしまう可能性はありませんでしょうか。 > > 情報ありがとうございます。 > libedit についてもう少し調査が必要ですね。 > > ヒストリに関する単体テストを書いてみて、 > いろいろな環境でテストしてみたいと思います。 ということで、Readline::HISTORYに対する単体テストを作成しました。 Readline::HISTORYの全てのメソッドのテストを記述しています。 本メールの下部に記述します。 このテストを GNU Readline と Mac OSX 10.5 の libedit で 実行した結果を以下に示します。 ----- ここから ----- GNU Readline ~/work/ruby/ruby_1_9 $ ./ruby19trunk -Iext/readline/gnu-readline test/readline/test_readline_history.rb Loaded suite test/readline/test_readline_history Started ................. Finished in 0.004555 seconds. 17 tests, 159 assertions, 0 failures, 0 errors libedit ~/work/ruby/ruby_1_9 $ ./ruby19trunk -Iext/readline/editline test/readline/test_readline_history.rb Loaded suite test/readline/test_readline_history Started ..F..FEF..EEFFF.. Finished in 0.017272 seconds. 1) Failure: test_each(Readline::TestHistory) [test/readline/test_readline_history.rb:144:in `block in test_each' test/readline/test_readline_history.rb:142:in `each' test/readline/test_readline_history.rb:142:in `test_each']: <"1:a"> expected but was <"2:b">. 2) Failure: test_get(Readline::TestHistory) [test/readline/test_readline_history.rb:22:in `block in test_get' test/readline/test_readline_history.rb:21:in `each' test/readline/test_readline_history.rb:21:in `each_with_index' test/readline/test_readline_history.rb:21:in `test_get']: <"1:a"> expected but was <"2:b">. 3) Error: test_get__negative(Readline::TestHistory): IndexError: invalid index test/readline/test_readline_history.rb:29:in `[]' test/readline/test_readline_history.rb:29:in `block in test_get__negative' test/readline/test_readline_history.rb:28:in `each' test/readline/test_readline_history.rb:28:in `test_get__negative' 4) Failure: test_get__out_of_range(Readline::TestHistory) [test/readline/test_readline_history.rb:37:in `block in test_get__out_of_range' test/readline/test_readline_history.rb:36:in `each' test/readline/test_readline_history.rb:36:in `test_get__out_of_range']: i=<-6>. exception expected but none was thrown. 5) Error: test_push(Readline::TestHistory): IndexError: invalid index test/readline/test_readline_history.rb:85:in `[]' test/readline/test_readline_history.rb:85:in `block in test_push' test/readline/test_readline_history.rb:83:in `times' test/readline/test_readline_history.rb:83:in `test_push' 6) Error: test_push__operator(Readline::TestHistory): IndexError: invalid index test/readline/test_readline_history.rb:93:in `[]' test/readline/test_readline_history.rb:93:in `block in test_push__operator' test/readline/test_readline_history.rb:91:in `times' test/readline/test_readline_history.rb:91:in `test_push__operator' 7) Failure: test_push__plural(Readline::TestHistory) [test/readline/test_readline_history.rb:101:in `block in test_push__plural' test/readline/test_readline_history.rb:100:in `each' test/readline/test_readline_history.rb:100:in `test_push__plural']: <"0"> expected but was <"1">. 8) Failure: test_set(Readline::TestHistory) [test/readline/test_readline_history.rb:56:in `block in test_set' test/readline/test_readline_history.rb:53:in `times' test/readline/test_readline_history.rb:53:in `test_set']: <"set: 0"> expected but was <"2:b">. 9) Failure: test_set__out_of_range(Readline::TestHistory) [test/readline/test_readline_history.rb:68:in `block in test_set__out_of_range' test/readline/test_readline_history.rb:67:in `each' test/readline/test_readline_history.rb:67:in `test_set__out_of_range']: index=<-6>. exception expected but none was thrown. 17 tests, 98 assertions, 6 failures, 3 errors ----- ここまで ----- これからテストが通るようにext/readline/readline.cを修正します。 以下、パッチ。 ----- ここから ----- Index: test/readline/test_readline_history.rb =================================================================== --- test/readline/test_readline_history.rb (revision 0) +++ test/readline/test_readline_history.rb (revision 0) @@ -0,0 +1,238 @@ +begin + require "readline" +rescue LoadError +else + require "test/unit" +end + +class Readline::TestHistory < Test::Unit::TestCase + include Readline + + def setup + clear_history + end + + def test_to_s + assert_equal("HISTORY", HISTORY.to_s) + end + + def test_get + lines = push_history(5) + lines.each_with_index do |s, i| + assert_equal(s, HISTORY[i]) + end + end + + def test_get__negative + lines = push_history(5) + (1..5).each do |i| + assert_equal(lines[-i], HISTORY[-i]) + end + end + + def test_get__out_of_range + lines = push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raise(IndexError, "i=<#{i}>") do + HISTORY[i] + end + end + + invalid_indexes = [100_000_000_000_000_000_000, + -100_000_000_000_000_000_000] + invalid_indexes.each do |i| + assert_raise(RangeError, "i=<#{i}>") do + HISTORY[i] + end + end + end + + def test_set + lines = push_history(5) + 5.times do |i| + expected = "set: #{i}" + HISTORY[i] = expected + assert_equal(expected, HISTORY[i]) + end + end + + def test_set__out_of_range + assert_raise(IndexError, "index=<0>") do + HISTORY[0] = "set: 0" + end + + lines = push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raise(IndexError, "index=<#{i}>") do + HISTORY[i] = "set: #{i}" + end + end + + invalid_indexes = [100_000_000_000_000_000_000, + -100_000_000_000_000_000_000] + invalid_indexes.each do |i| + assert_raise(RangeError, "index=<#{i}>") do + HISTORY[i] = "set: #{i}" + end + end + end + + def test_push + 5.times do |i| + assert_equal(HISTORY, HISTORY.push(i.to_s)) + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(5, HISTORY.length) + end + + def test_push__operator + 5.times do |i| + assert_equal(HISTORY, HISTORY << i.to_s) + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(5, HISTORY.length) + end + + def test_push__plural + assert_equal(HISTORY, HISTORY.push("0", "1", "2", "3", "4")) + (0..4).each do |i| + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(5, HISTORY.length) + + assert_equal(HISTORY, HISTORY.push("5", "6", "7", "8", "9")) + (5..9).each do |i| + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(10, HISTORY.length) + end + + def test_pop + assert_equal(nil, HISTORY.pop) + + lines = push_history(5) + (1..5).each do |i| + assert_equal(lines[-i], HISTORY.pop) + assert_equal(lines.length - i, HISTORY.length) + end + + assert_equal(nil, HISTORY.pop) + end + + def test_shift + assert_equal(nil, HISTORY.shift) + + lines = push_history(5) + (0..4).each do |i| + assert_equal(lines[i], HISTORY.shift) + assert_equal(lines.length - (i + 1), HISTORY.length) + end + + assert_equal(nil, HISTORY.shift) + end + + def test_each + HISTORY.each do |s| + assert(false) # not reachable + end + lines = push_history(5) + i = 0 + HISTORY.each do |s| + assert_equal(HISTORY[i], s) + assert_equal(lines[i], s) + i += 1 + end + end + + def test_each__enumerator + e = HISTORY.each + assert_instance_of(Enumerable::Enumerator, e) + end + + def test_length + assert_equal(0, HISTORY.length) + push_history(1) + assert_equal(1, HISTORY.length) + push_history(4) + assert_equal(5, HISTORY.length) + clear_history + assert_equal(0, HISTORY.length) + end + + def test_empty_p + 2.times do + assert(HISTORY.empty?) + HISTORY.push("s") + assert_equal(false, HISTORY.empty?) + HISTORY.pop + assert(HISTORY.empty?) + end + end + + def test_delete_at + lines = push_history(5) + (0..4).each do |i| + assert_equal(lines[i], HISTORY.delete_at(0)) + end + assert(HISTORY.empty?) + + lines = push_history(5) + (1..5).each do |i| + assert_equal(lines[lines.length - i], HISTORY.delete_at(-1)) + end + assert(HISTORY.empty?) + + lines = push_history(5) + assert_equal(lines[0], HISTORY.delete_at(0)) + assert_equal(lines[4], HISTORY.delete_at(3)) + assert_equal(lines[1], HISTORY.delete_at(0)) + assert_equal(lines[3], HISTORY.delete_at(1)) + assert_equal(lines[2], HISTORY.delete_at(0)) + assert(HISTORY.empty?) + end + + def test_delete_at__out_of_range + assert_raise(IndexError, "index=<0>") do + HISTORY.delete_at(0) + end + + lines = push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raise(IndexError, "index=<#{i}>") do + HISTORY.delete_at(i) + end + end + + invalid_indexes = [100_000_000_000_000_000_000, + -100_000_000_000_000_000_000] + invalid_indexes.each do |i| + assert_raise(RangeError, "index=<#{i}>") do + HISTORY.delete_at(i) + end + end + end + + private + + def push_history(num) + lines = [] + num.times do |i| + s = "a" + i.times do + s = s.succ + end + lines.push("#{i + 1}:#{s}") + end + HISTORY.push(*lines) + return lines + end + + def clear_history + while HISTORY.pop + end + assert(HISTORY.empty?) + end +end if defined?(::Readline) && defined?(::Readline::HISTORY)