1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_fiber.rb
Jeremy Evans fa8ac91e95
Fix Fiber#transfer
Fiber#transfer previously made it impossible to resume the fiber
if it was transferred to (no resuming the target of Fiber#transfer).
However, the documentation specifies that you cannot resume a fiber
that has transferred to another fiber (no resuming the source of
Fiber#transfer), unless control is transferred back.

Fix the code by setting the transferred flag on the current/source
fiber, and unsetting the transferred flag on the target fiber.

Fixes [Bug #9664]
Fixes [Bug #12555]
2019-10-21 16:54:58 -07:00

454 lines
11 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
require 'fiber'
EnvUtil.suppress_warning {require 'continuation'}
require 'tmpdir'
class TestFiber < Test::Unit::TestCase
def test_normal
assert_equal(:ok2,
Fiber.new{|e|
assert_equal(:ok1, e)
Fiber.yield :ok2
}.resume(:ok1)
)
assert_equal([:a, :b], Fiber.new{|a, b| [a, b]}.resume(:a, :b))
end
def test_argument
assert_equal(4, Fiber.new {|i=4| i}.resume)
end
def test_term
assert_equal(:ok, Fiber.new{:ok}.resume)
assert_equal([:a, :b, :c, :d, :e],
Fiber.new{
Fiber.new{
Fiber.new{
Fiber.new{
[:a]
}.resume + [:b]
}.resume + [:c]
}.resume + [:d]
}.resume + [:e])
end
def test_many_fibers
max = 10_000
assert_equal(max, max.times{
Fiber.new{}
})
GC.start # force collect created fibers
assert_equal(max,
max.times{|i|
Fiber.new{
}.resume
}
)
GC.start # force collect created fibers
end
def test_many_fibers_with_threads
assert_normal_exit <<-SRC, timeout: 60
max = 1000
@cnt = 0
(1..100).map{|ti|
Thread.new{
max.times{|i|
Fiber.new{
@cnt += 1
}.resume
}
}
}.each{|t|
t.join
}
SRC
end
def test_error
assert_raise(ArgumentError){
Fiber.new # Fiber without block
}
f = Fiber.new{}
Thread.new{
assert_raise(FiberError){ # Fiber yielding across thread
f.resume
}
}.join
assert_raise(FiberError){
f = Fiber.new{}
f.resume
f.resume
}
assert_raise(RuntimeError){
Fiber.new{
@c = callcc{|c| @c = c}
}.resume
@c.call # cross fiber callcc
}
assert_raise(RuntimeError){
Fiber.new{
raise
}.resume
}
assert_raise(FiberError){
Fiber.yield
}
assert_raise(FiberError){
fib = Fiber.new{
fib.resume
}
fib.resume
}
assert_raise(FiberError){
fib = Fiber.new{
Fiber.new{
fib.resume
}.resume
}
fib.resume
}
assert_raise(FiberError){
fib = Fiber.new{}
fib.raise "raise in unborn fiber"
}
assert_raise(FiberError){
fib = Fiber.new{}
fib.resume
fib.raise "raise in dead fiber"
}
end
def test_return
assert_raise(LocalJumpError){
Fiber.new do
return
end.resume
}
end
def test_throw
assert_raise(UncaughtThrowError){
Fiber.new do
throw :a
end.resume
}
end
def test_raise
assert_raise(ZeroDivisionError){
Fiber.new do
1/0
end.resume
}
assert_raise(RuntimeError){
fib = Fiber.new{ Fiber.yield }
fib.resume
fib.raise "raise and propagate"
}
assert_nothing_raised{
fib = Fiber.new do
begin
Fiber.yield
rescue
end
end
fib.resume
fib.raise "rescue in fiber"
}
fib = Fiber.new do
begin
Fiber.yield
rescue
Fiber.yield :ok
end
end
fib.resume
assert_equal(:ok, fib.raise)
end
def test_transfer
ary = []
f2 = nil
f1 = Fiber.new{
ary << f2.transfer(:foo)
:ok
}
f2 = Fiber.new{
ary << f1.transfer(:baz)
:ng
}
assert_equal(:ok, f1.transfer)
assert_equal([:baz], ary)
end
def test_tls
#
def tvar(var, val)
old = Thread.current[var]
begin
Thread.current[var] = val
yield
ensure
Thread.current[var] = old
end
end
fb = Fiber.new {
assert_equal(nil, Thread.current[:v]); tvar(:v, :x) {
assert_equal(:x, Thread.current[:v]); Fiber.yield
assert_equal(:x, Thread.current[:v]); }
assert_equal(nil, Thread.current[:v]); Fiber.yield
raise # unreachable
}
assert_equal(nil, Thread.current[:v]); tvar(:v,1) {
assert_equal(1, Thread.current[:v]); tvar(:v,3) {
assert_equal(3, Thread.current[:v]); fb.resume
assert_equal(3, Thread.current[:v]); }
assert_equal(1, Thread.current[:v]); }
assert_equal(nil, Thread.current[:v]); fb.resume
assert_equal(nil, Thread.current[:v]);
end
def test_alive
fib = Fiber.new{Fiber.yield}
assert_equal(true, fib.alive?)
fib.resume
assert_equal(true, fib.alive?)
fib.resume
assert_equal(false, fib.alive?)
end
def test_resume_self
f = Fiber.new {f.resume 1}
assert_equal(1, f.transfer)
end
def test_fiber_transfer_segv
assert_normal_exit %q{
require 'fiber'
f2 = nil
f1 = Fiber.new{ f2.resume }
f2 = Fiber.new{ f1.resume }
f1.transfer
}, '[ruby-dev:40833]'
assert_normal_exit %q{
require 'fiber'
Fiber.new{}.resume
1.times{Fiber.current.transfer}
}
end
def test_resume_root_fiber
Thread.new do
assert_raise(FiberError) do
Fiber.current.resume
end
end.join
end
def test_gc_root_fiber
bug4612 = '[ruby-core:35891]'
assert_normal_exit %q{
require 'fiber'
GC.stress = true
Thread.start{ Fiber.current; nil }.join
GC.start
}, bug4612
end
def test_mark_fiber
bug13875 = '[ruby-core:82681]'
assert_normal_exit %q{
GC.stress = true
up = 1.upto(10)
down = 10.downto(1)
up.zip(down) {|a, b| a + b == 11 or fail 'oops'}
}, bug13875
end
def test_no_valid_cfp
bug5083 = '[ruby-dev:44208]'
assert_equal([], Fiber.new(&Module.method(:nesting)).resume, bug5083)
assert_instance_of(Class, Fiber.new(&Class.new.method(:undef_method)).resume(:to_s), bug5083)
end
def test_prohibit_resume_transferred_fiber
assert_raise(FiberError){
root_fiber = Fiber.current
f = Fiber.new{
root_fiber.transfer
}
f.transfer
f.resume
}
assert_raise(FiberError){
g=nil
f=Fiber.new{
g.transfer
}
g=Fiber.new{
f.resume
}
f.resume
}
end
def test_fork_from_fiber
skip 'fork not supported' unless Process.respond_to?(:fork)
pid = nil
bug5700 = '[ruby-core:41456]'
assert_nothing_raised(bug5700) do
Fiber.new do
pid = fork do
xpid = nil
Fiber.new {
xpid = fork do
# enough to trigger GC on old root fiber
count = 10000
count = 1000 if /openbsd/i =~ RUBY_PLATFORM
count.times do
Fiber.new {}.transfer
Fiber.new { Fiber.yield }
end
exit!(0)
end
}.transfer
_, status = Process.waitpid2(xpid)
exit!(status.success?)
end
end.resume
end
pid, status = Process.waitpid2(pid)
assert_equal(0, status.exitstatus, bug5700)
assert_equal(false, status.signaled?, bug5700)
end
def test_exit_in_fiber
bug5993 = '[ruby-dev:45218]'
assert_nothing_raised(bug5993) do
Thread.new{ Fiber.new{ Thread.exit }.resume; raise "unreachable" }.join
end
end
def test_fatal_in_fiber
assert_in_out_err(["-r-test-/fatal/rb_fatal", "-e", <<-EOS], "", [], /ok/)
Fiber.new{
rb_fatal "ok"
}.resume
puts :ng # unreachable.
EOS
end
def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true
env = {}
env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size
env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size
out = Dir.mktmpdir("test_fiber") {|tmpdir|
out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir, timeout: 30)
assert(!status.signaled?, FailDesc[status, nil, err])
out
}
use_length ? out.length : out
end
def test_stack_size
h_default = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', nil, nil, false))
h_0 = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 0, 0, false))
h_large = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 1024 * 1024 * 5, 1024 * 1024 * 10, false))
assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size])
assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size])
assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size])
assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size])
# check VM machine stack size
script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume'
size_default = invoke_rec script, nil, nil
assert_operator(size_default, :>, 0)
size_0 = invoke_rec script, 0, nil
assert_operator(size_default, :>, size_0)
size_large = invoke_rec script, 1024 * 1024 * 5, nil
assert_operator(size_default, :<, size_large)
return if /mswin|mingw/ =~ RUBY_PLATFORM
# check machine stack size
# Note that machine stack size may not change size (depend on OSs)
script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume'
vm_stack_size = 1024 * 1024
size_default = invoke_rec script, vm_stack_size, nil
size_0 = invoke_rec script, vm_stack_size, 0
assert_operator(size_default, :>=, size_0)
size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10
assert_operator(size_default, :<=, size_large)
end
def test_separate_lastmatch
bug7678 = '[ruby-core:51331]'
/a/ =~ "a"
m1 = $~
m2 = nil
Fiber.new do
/b/ =~ "b"
m2 = $~
end.resume
assert_equal("b", m2[0])
assert_equal(m1, $~, bug7678)
end
def test_separate_lastline
bug7678 = '[ruby-core:51331]'
$_ = s1 = "outer"
s2 = nil
Fiber.new do
s2 = "inner"
end.resume
assert_equal("inner", s2)
assert_equal(s1, $_, bug7678)
end
def test_new_symbol_proc
bug = '[ruby-core:80147] [Bug #13313]'
assert_ruby_status([], "#{<<-"begin;"}\n#{<<-'end;'}", bug)
begin;
exit("1" == Fiber.new(&:to_s).resume(1))
end;
end
def test_to_s
f = Fiber.new do
assert_match(/resumed/, f.to_s)
Fiber.yield
end
assert_match(/created/, f.to_s)
f.resume
assert_match(/suspended/, f.to_s)
f.resume
assert_match(/terminated/, f.to_s)
assert_match(/resumed/, Fiber.current.to_s)
end
def test_create_fiber_in_new_thread
ret = Thread.new{
Thread.new{
Fiber.new{Fiber.yield :ok}.resume
}.value
}.value
assert_equal :ok, ret, '[Bug #14642]'
end
def test_machine_stack_gc
assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 10
enum = Enumerator.new { |y| y << 1 }
thread = Thread.new { enum.peek }
thread.join
sleep 5 # pause until thread cache wait time runs out. Native thread exits.
GC.start
RUBY
end
end