зеркало из https://github.com/github/ruby.git
* enumerator.c: implement Enumerator#{next_values,peek_values,feed}
and StopIteration#result. [ruby-dev:39109] (struct enumerator): replace no_next by stop_exc. new field feedvalue. (enumerator_mark): mark feedvalue and stop_exc. (enumerator_init): initialize feedvalue and stop_exc. (enumerator_init_copy): initialize feedvalue. (next_ii): send yield arguments as an array. return feedvalue. (next_i): generate StopIteration exception here. set result. (next_init): initialize feedvalue. (enumerator_next_values): new method Enumerator#next_values. (ary2sv): new function. (enumerator_peek_values): new method Enumerator#peek_values. (enumerator_feed): new method Enumerator#feed. (yielder_yield): return the yield value. (generator_each): return the iterator value. (stop_result): new method StopIteration#result. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@24587 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
Родитель
b307bd9847
Коммит
3b4949ffa8
20
ChangeLog
20
ChangeLog
|
@ -1,3 +1,23 @@
|
|||
Thu Aug 20 01:28:42 2009 Tanaka Akira <akr@fsij.org>
|
||||
|
||||
* enumerator.c: implement Enumerator#{next_values,peek_values,feed}
|
||||
and StopIteration#result. [ruby-dev:39109]
|
||||
(struct enumerator): replace no_next by stop_exc.
|
||||
new field feedvalue.
|
||||
(enumerator_mark): mark feedvalue and stop_exc.
|
||||
(enumerator_init): initialize feedvalue and stop_exc.
|
||||
(enumerator_init_copy): initialize feedvalue.
|
||||
(next_ii): send yield arguments as an array. return feedvalue.
|
||||
(next_i): generate StopIteration exception here. set result.
|
||||
(next_init): initialize feedvalue.
|
||||
(enumerator_next_values): new method Enumerator#next_values.
|
||||
(ary2sv): new function.
|
||||
(enumerator_peek_values): new method Enumerator#peek_values.
|
||||
(enumerator_feed): new method Enumerator#feed.
|
||||
(yielder_yield): return the yield value.
|
||||
(generator_each): return the iterator value.
|
||||
(stop_result): new method StopIteration#result.
|
||||
|
||||
Thu Aug 20 01:06:48 2009 Yukihiro Matsumoto <matz@ruby-lang.org>
|
||||
|
||||
* dir.c (DEFINE_STRUCT_DIRENT): use union to allocate sufficient
|
||||
|
|
9
NEWS
9
NEWS
|
@ -8,7 +8,6 @@ reference information is supplied with. For a full list of changes
|
|||
with all sufficient information, see the ChangeLog file.
|
||||
|
||||
== Changes since the 1.9.1 release
|
||||
|
||||
=== Library updates (outstanding ones only)
|
||||
|
||||
* builtin classes
|
||||
|
@ -22,6 +21,13 @@ with all sufficient information, see the ChangeLog file.
|
|||
* Dir.home
|
||||
|
||||
* Enumerator
|
||||
* new methods:
|
||||
* Enumerator#peek
|
||||
* Enumerator#next_values
|
||||
* Enumerator#peek_values
|
||||
* Enumerator#feed
|
||||
* StopIteration#result
|
||||
|
||||
* extended methods:
|
||||
* #with_index accepts an optional argument that specifies the
|
||||
index number to start with, defaulted to 0.
|
||||
|
@ -29,6 +35,7 @@ with all sufficient information, see the ChangeLog file.
|
|||
* incompatible changes:
|
||||
* #rewind now calls the "rewind" method of the enclosed object
|
||||
if defined.
|
||||
* #next doesn't clear the position at end.
|
||||
|
||||
* IO
|
||||
* new method:
|
||||
|
|
319
enumerator.c
319
enumerator.c
|
@ -19,6 +19,60 @@
|
|||
*
|
||||
* A class which provides a method `each' to be used as an Enumerable
|
||||
* object.
|
||||
*
|
||||
* An enumerator can be created by following methods.
|
||||
* - Kernel#to_enum
|
||||
* - Kernel#enum_for
|
||||
* - Enumerator.new
|
||||
*
|
||||
* Also, most iteration methods without a block returns an enumerator.
|
||||
* For example, Array#map returns an enumerator if no block given.
|
||||
* The enumerator has with_index.
|
||||
* So ary.map.with_index works as follows.
|
||||
*
|
||||
* p [1,2,3].map.with_index {|o, i| o+i } #=> [1, 3, 5]
|
||||
*
|
||||
* An enumerator object can be used as an external iterator.
|
||||
* I.e. Enumerator#next returns the next value of the iterator.
|
||||
* Enumerator#next raises StopIteration at end.
|
||||
*
|
||||
* e = [1,2,3].each # enumerator object.
|
||||
* p e.next #=> 1
|
||||
* p e.next #=> 2
|
||||
* p e.next #=> 3
|
||||
* p e.next #raises StopIteration
|
||||
*
|
||||
* An external iterator can be used to implement an internal iterator as follows.
|
||||
*
|
||||
* def ext_each(e)
|
||||
* while true
|
||||
* begin
|
||||
* vs = e.next_values
|
||||
* rescue StopIteration
|
||||
* return $!.result
|
||||
* end
|
||||
* y = yield *vs
|
||||
* e.feed y
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* o = Object.new
|
||||
* def o.each
|
||||
* p yield
|
||||
* p yield 1
|
||||
* p yield(1, 2)
|
||||
* 3
|
||||
* end
|
||||
*
|
||||
* # use o.each as an internal iterator directly.
|
||||
* p o.each {|*x| p x; [:b, *x] }
|
||||
* #=> [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3
|
||||
*
|
||||
* # convert o.each to an external external iterator for
|
||||
* # implementing an internal iterator.
|
||||
* p ext_each(o.to_enum) {|*x| p x; [:b, *x] }
|
||||
* #=> [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3
|
||||
*
|
||||
*/
|
||||
VALUE rb_cEnumerator;
|
||||
static ID id_rewind, id_each;
|
||||
|
@ -33,7 +87,8 @@ struct enumerator {
|
|||
VALUE fib;
|
||||
VALUE dst;
|
||||
VALUE lookahead;
|
||||
VALUE no_next;
|
||||
VALUE feedvalue;
|
||||
VALUE stop_exc;
|
||||
};
|
||||
|
||||
static VALUE rb_cGenerator, rb_cYielder;
|
||||
|
@ -61,6 +116,8 @@ enumerator_mark(void *p)
|
|||
rb_gc_mark(ptr->fib);
|
||||
rb_gc_mark(ptr->dst);
|
||||
rb_gc_mark(ptr->lookahead);
|
||||
rb_gc_mark(ptr->feedvalue);
|
||||
rb_gc_mark(ptr->stop_exc);
|
||||
}
|
||||
|
||||
static struct enumerator *
|
||||
|
@ -284,7 +341,8 @@ enumerator_init(VALUE enum_obj, VALUE obj, VALUE meth, int argc, VALUE *argv)
|
|||
ptr->fib = 0;
|
||||
ptr->dst = Qnil;
|
||||
ptr->lookahead = Qundef;
|
||||
ptr->no_next = Qfalse;
|
||||
ptr->feedvalue = Qundef;
|
||||
ptr->stop_exc = Qfalse;
|
||||
|
||||
return enum_obj;
|
||||
}
|
||||
|
@ -365,6 +423,7 @@ enumerator_init_copy(VALUE obj, VALUE orig)
|
|||
ptr1->args = ptr0->args;
|
||||
ptr1->fib = 0;
|
||||
ptr1->lookahead = Qundef;
|
||||
ptr1->feedvalue = Qundef;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -502,8 +561,15 @@ enumerator_with_object(VALUE obj, VALUE memo)
|
|||
static VALUE
|
||||
next_ii(VALUE i, VALUE obj, int argc, VALUE *argv)
|
||||
{
|
||||
rb_fiber_yield(argc, argv);
|
||||
return Qnil;
|
||||
struct enumerator *e = enumerator_ptr(obj);
|
||||
VALUE feedvalue = Qnil;
|
||||
VALUE args = rb_ary_new4(argc, argv);
|
||||
rb_fiber_yield(1, &args);
|
||||
if (e->feedvalue != Qundef) {
|
||||
feedvalue = e->feedvalue;
|
||||
e->feedvalue = Qundef;
|
||||
}
|
||||
return feedvalue;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
|
@ -511,9 +577,11 @@ next_i(VALUE curr, VALUE obj)
|
|||
{
|
||||
struct enumerator *e = enumerator_ptr(obj);
|
||||
VALUE nil = Qnil;
|
||||
VALUE result;
|
||||
|
||||
rb_block_call(obj, id_each, 0, 0, next_ii, obj);
|
||||
e->no_next = Qtrue;
|
||||
result = rb_block_call(obj, id_each, 0, 0, next_ii, obj);
|
||||
e->stop_exc = rb_exc_new2(rb_eStopIteration, "iteration reached at end");
|
||||
rb_ivar_set(e->stop_exc, rb_intern("result"), result);
|
||||
return rb_fiber_yield(1, &nil);
|
||||
}
|
||||
|
||||
|
@ -524,6 +592,102 @@ next_init(VALUE obj, struct enumerator *e)
|
|||
e->dst = curr;
|
||||
e->fib = rb_fiber_new(next_i, obj);
|
||||
e->lookahead = Qundef;
|
||||
e->feedvalue = Qundef;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* e.next_values => array
|
||||
*
|
||||
* Returns the next object as an array in the enumerator,
|
||||
* and move the internal position forward.
|
||||
* When the position reached at the end, StopIteration is raised.
|
||||
*
|
||||
* This method can be used to distinguish <code>yield</code> and <code>yield nil</code>.
|
||||
*
|
||||
* o = Object.new
|
||||
* def o.each
|
||||
* yield
|
||||
* yield 1
|
||||
* yield 1, 2
|
||||
* yield nil
|
||||
* yield [1, 2]
|
||||
* end
|
||||
* e = o.to_enum
|
||||
* p e.next_values
|
||||
* p e.next_values
|
||||
* p e.next_values
|
||||
* p e.next_values
|
||||
* p e.next_values
|
||||
* e = o.to_enum
|
||||
* p e.next
|
||||
* p e.next
|
||||
* p e.next
|
||||
* p e.next
|
||||
* p e.next
|
||||
*
|
||||
* # result
|
||||
* # next_values next
|
||||
* # [] nil
|
||||
* # [1] 1
|
||||
* # [1, 2] [1, 2]
|
||||
* # [nil] nil
|
||||
* # [[1, 2]] [1, 2]
|
||||
*
|
||||
* Note that enumeration sequence by next_values method does not affect other
|
||||
* non-external enumeration methods, unless underlying iteration
|
||||
* methods itself has side-effect, e.g. IO#each_line.
|
||||
*
|
||||
*/
|
||||
|
||||
static VALUE
|
||||
enumerator_next_values(VALUE obj)
|
||||
{
|
||||
struct enumerator *e = enumerator_ptr(obj);
|
||||
VALUE curr, v;
|
||||
|
||||
if (e->lookahead != Qundef) {
|
||||
v = e->lookahead;
|
||||
e->lookahead = Qundef;
|
||||
return v;
|
||||
}
|
||||
|
||||
if (e->stop_exc)
|
||||
rb_exc_raise(e->stop_exc);
|
||||
|
||||
curr = rb_fiber_current();
|
||||
|
||||
if (!e->fib || !rb_fiber_alive_p(e->fib)) {
|
||||
next_init(obj, e);
|
||||
}
|
||||
|
||||
v = rb_fiber_resume(e->fib, 1, &curr);
|
||||
if (e->stop_exc) {
|
||||
e->fib = 0;
|
||||
e->dst = Qnil;
|
||||
e->lookahead = Qundef;
|
||||
e->feedvalue = Qundef;
|
||||
rb_exc_raise(e->stop_exc);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ary2sv(VALUE args)
|
||||
{
|
||||
if (TYPE(args) != T_ARRAY)
|
||||
return args;
|
||||
|
||||
switch (RARRAY_LEN(args)) {
|
||||
case 0:
|
||||
return Qnil;
|
||||
|
||||
case 1:
|
||||
return RARRAY_PTR(args)[0];
|
||||
|
||||
default:
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -542,32 +706,49 @@ next_init(VALUE obj, struct enumerator *e)
|
|||
|
||||
static VALUE
|
||||
enumerator_next(VALUE obj)
|
||||
{
|
||||
VALUE vs = enumerator_next_values(obj);
|
||||
return ary2sv(vs);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* e.peek_values => array
|
||||
*
|
||||
* Returns the next object as an array in the enumerator,
|
||||
* but don't move the internal position forward.
|
||||
* When the position reached at the end, StopIteration is raised.
|
||||
*
|
||||
* o = Object.new
|
||||
* def o.each
|
||||
* yield
|
||||
* yield 1
|
||||
* yield 1, 2
|
||||
* end
|
||||
* e = o.to_enum
|
||||
* p e.peek_values #=> []
|
||||
* e.next
|
||||
* p e.peek_values #=> [1]
|
||||
* e.next
|
||||
* p e.peek_values #=> [1, 2]
|
||||
* e.next
|
||||
* p e.peek_values # raises StopIteration
|
||||
*
|
||||
*/
|
||||
|
||||
static VALUE
|
||||
enumerator_peek_values(VALUE obj)
|
||||
{
|
||||
struct enumerator *e = enumerator_ptr(obj);
|
||||
VALUE curr, v;
|
||||
VALUE v;
|
||||
|
||||
if (e->lookahead != Qundef) {
|
||||
v = e->lookahead;
|
||||
e->lookahead = Qundef;
|
||||
return v;
|
||||
}
|
||||
|
||||
if (e->no_next)
|
||||
rb_raise(rb_eStopIteration, "iteration reached at end");
|
||||
|
||||
curr = rb_fiber_current();
|
||||
|
||||
if (!e->fib || !rb_fiber_alive_p(e->fib)) {
|
||||
next_init(obj, e);
|
||||
}
|
||||
|
||||
v = rb_fiber_resume(e->fib, 1, &curr);
|
||||
if (e->no_next) {
|
||||
e->fib = 0;
|
||||
e->dst = Qnil;
|
||||
e->lookahead = Qundef;
|
||||
rb_raise(rb_eStopIteration, "iteration reached at end");
|
||||
}
|
||||
v = enumerator_next_values(obj);
|
||||
e->lookahead = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
@ -583,18 +764,46 @@ enumerator_next(VALUE obj)
|
|||
|
||||
static VALUE
|
||||
enumerator_peek(VALUE obj)
|
||||
{
|
||||
VALUE vs = enumerator_peek_values(obj);
|
||||
return ary2sv(vs);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* e.feed obj => nil
|
||||
*
|
||||
* Set the value for the next yield in the enumerator returns.
|
||||
*
|
||||
* If the value is not set, yield returns nil.
|
||||
*
|
||||
* This value is cleared after used.
|
||||
*
|
||||
* o = Object.new
|
||||
* def o.each
|
||||
* p yield #=> 1
|
||||
* p yield #=> nil
|
||||
* p yield
|
||||
* end
|
||||
* e = o.to_enum
|
||||
* e.next
|
||||
* e.feed 1
|
||||
* e.next
|
||||
* e.next
|
||||
*
|
||||
*/
|
||||
|
||||
static VALUE
|
||||
enumerator_feed(VALUE obj, VALUE v)
|
||||
{
|
||||
struct enumerator *e = enumerator_ptr(obj);
|
||||
VALUE v;
|
||||
|
||||
if (e->lookahead != Qundef) {
|
||||
v = e->lookahead;
|
||||
return v;
|
||||
if (e->feedvalue != Qundef) {
|
||||
rb_raise(rb_eTypeError, "feed value already set");
|
||||
}
|
||||
e->feedvalue = v;
|
||||
|
||||
v = enumerator_next(obj);
|
||||
e->lookahead = v;
|
||||
return v;
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -617,7 +826,8 @@ enumerator_rewind(VALUE obj)
|
|||
e->fib = 0;
|
||||
e->dst = Qnil;
|
||||
e->lookahead = Qundef;
|
||||
e->no_next = Qfalse;
|
||||
e->feedvalue = Qundef;
|
||||
e->stop_exc = Qfalse;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -754,9 +964,7 @@ yielder_yield(VALUE obj, VALUE args)
|
|||
{
|
||||
struct yielder *ptr = yielder_ptr(obj);
|
||||
|
||||
rb_proc_call(ptr->proc, args);
|
||||
|
||||
return obj;
|
||||
return rb_proc_call(ptr->proc, args);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
|
@ -883,9 +1091,42 @@ generator_each(VALUE obj)
|
|||
|
||||
yielder = yielder_new();
|
||||
|
||||
rb_proc_call(ptr->proc, rb_ary_new3(1, yielder));
|
||||
return rb_proc_call(ptr->proc, rb_ary_new3(1, yielder));
|
||||
}
|
||||
|
||||
return obj;
|
||||
/*
|
||||
* StopIteration
|
||||
*/
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* stopiteration.result => value
|
||||
*
|
||||
* Returns the return value of the iterator.
|
||||
*
|
||||
*
|
||||
* o = Object.new
|
||||
* def o.each
|
||||
* yield 1
|
||||
* yield 2
|
||||
* yield 3
|
||||
* 100
|
||||
* end
|
||||
* e = o.to_enum
|
||||
* p e.next #=> 1
|
||||
* p e.next #=> 2
|
||||
* p e.next #=> 3
|
||||
* begin
|
||||
* e.next
|
||||
* rescue StopIteration
|
||||
* p $!.result #=> 100
|
||||
* end
|
||||
*
|
||||
*/
|
||||
static VALUE
|
||||
stop_result(VALUE self)
|
||||
{
|
||||
return rb_attr_get(self, rb_intern("result"));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -909,12 +1150,16 @@ Init_Enumerator(void)
|
|||
rb_define_method(rb_cEnumerator, "each_with_object", enumerator_with_object, 1);
|
||||
rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, -1);
|
||||
rb_define_method(rb_cEnumerator, "with_object", enumerator_with_object, 1);
|
||||
rb_define_method(rb_cEnumerator, "next_values", enumerator_next_values, 0);
|
||||
rb_define_method(rb_cEnumerator, "peek_values", enumerator_peek_values, 0);
|
||||
rb_define_method(rb_cEnumerator, "next", enumerator_next, 0);
|
||||
rb_define_method(rb_cEnumerator, "peek", enumerator_peek, 0);
|
||||
rb_define_method(rb_cEnumerator, "feed", enumerator_feed, 1);
|
||||
rb_define_method(rb_cEnumerator, "rewind", enumerator_rewind, 0);
|
||||
rb_define_method(rb_cEnumerator, "inspect", enumerator_inspect, 0);
|
||||
|
||||
rb_eStopIteration = rb_define_class("StopIteration", rb_eIndexError);
|
||||
rb_define_method(rb_eStopIteration, "result", stop_result, 0);
|
||||
|
||||
/* Generator */
|
||||
rb_cGenerator = rb_define_class_under(rb_cEnumerator, "Generator", rb_cObject);
|
||||
|
|
|
@ -152,5 +152,130 @@ class TestEnumerator < Test::Unit::TestCase
|
|||
assert_raise(StopIteration) { e.next }
|
||||
assert_raise(StopIteration) { e.next }
|
||||
end
|
||||
|
||||
def test_stop_result
|
||||
a = [1]
|
||||
res = a.each {}
|
||||
e = a.each
|
||||
assert_equal(1, e.next)
|
||||
exc = assert_raise(StopIteration) { e.next }
|
||||
assert_equal(res, exc.result)
|
||||
end
|
||||
|
||||
def test_next_values
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield
|
||||
yield 1
|
||||
yield 1, 2
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal(nil, e.next)
|
||||
assert_equal(1, e.next)
|
||||
assert_equal([1,2], e.next)
|
||||
e = o.to_enum
|
||||
assert_equal([], e.next_values)
|
||||
assert_equal([1], e.next_values)
|
||||
assert_equal([1,2], e.next_values)
|
||||
end
|
||||
|
||||
def test_peek_values
|
||||
o = Object.new
|
||||
def o.each
|
||||
yield
|
||||
yield 1
|
||||
yield 1, 2
|
||||
end
|
||||
e = o.to_enum
|
||||
assert_equal(nil, e.peek)
|
||||
assert_equal(nil, e.next)
|
||||
assert_equal(1, e.peek)
|
||||
assert_equal(1, e.next)
|
||||
assert_equal([1,2], e.peek)
|
||||
assert_equal([1,2], e.next)
|
||||
e = o.to_enum
|
||||
assert_equal([], e.peek_values)
|
||||
assert_equal([], e.next_values)
|
||||
assert_equal([1], e.peek_values)
|
||||
assert_equal([1], e.next_values)
|
||||
assert_equal([1,2], e.peek_values)
|
||||
assert_equal([1,2], e.next_values)
|
||||
e = o.to_enum
|
||||
assert_equal([], e.peek_values)
|
||||
assert_equal(nil, e.next)
|
||||
assert_equal([1], e.peek_values)
|
||||
assert_equal(1, e.next)
|
||||
assert_equal([1,2], e.peek_values)
|
||||
assert_equal([1,2], e.next)
|
||||
e = o.to_enum
|
||||
assert_equal(nil, e.peek)
|
||||
assert_equal([], e.next_values)
|
||||
assert_equal(1, e.peek)
|
||||
assert_equal([1], e.next_values)
|
||||
assert_equal([1,2], e.peek)
|
||||
assert_equal([1,2], e.next_values)
|
||||
end
|
||||
|
||||
def test_feed
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum(:each, ary)
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.feed 2
|
||||
e.next
|
||||
e.feed 3
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal([1,2,3], ary)
|
||||
end
|
||||
|
||||
def test_feed_mixed
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum(:each, ary)
|
||||
e.next
|
||||
e.feed 1
|
||||
e.next
|
||||
e.next
|
||||
e.feed 3
|
||||
assert_raise(StopIteration) { e.next }
|
||||
assert_equal([1,nil,3], ary)
|
||||
end
|
||||
|
||||
def test_feed_twice
|
||||
o = Object.new
|
||||
def o.each(ary)
|
||||
ary << yield
|
||||
ary << yield
|
||||
ary << yield
|
||||
end
|
||||
ary = []
|
||||
e = o.to_enum(:each, ary)
|
||||
e.feed 1
|
||||
assert_raise(TypeError) { e.feed 2 }
|
||||
end
|
||||
|
||||
def test_feed_yielder
|
||||
x = nil
|
||||
e = Enumerator.new {|y| x = y.yield; 10 }
|
||||
e.next
|
||||
e.feed 100
|
||||
exc = assert_raise(StopIteration) { e.next }
|
||||
assert_equal(100, x)
|
||||
assert_equal(10, exc.result)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче