# Ractor.current returns a current ractor
assert_equal 'Ractor', %q{
  Ractor.current.class
}

# Ractor.new returns new Ractor
assert_equal 'Ractor', %q{
  Ractor.new{}.class
}

# Ractor.allocate is not supported
assert_equal "[:ok, :ok]", %q{
  rs = []
  begin
    Ractor.allocate
  rescue => e
    rs << :ok if e.message == 'allocator undefined for Ractor'
  end

  begin
    Ractor.new{}.dup
  rescue
    rs << :ok if e.message == 'allocator undefined for Ractor'
  end

  rs
}

# A Ractor can have a name
assert_equal 'test-name', %q{
  r = Ractor.new name: 'test-name' do
  end
  r.name
}

# If Ractor doesn't have a name, Ractor#name returns nil.
assert_equal 'nil', %q{
  r = Ractor.new do
  end
  r.name.inspect
}

# Raises exceptions if initialize with an invalid name
assert_equal 'ok', %q{
  begin
    r = Ractor.new(name: [{}]) {}
  rescue TypeError => e
    'ok'
  end
}

# Ractor.new must call with a block
assert_equal "must be called with a block", %q{
  begin
    Ractor.new
  rescue ArgumentError => e
    e.message
  end
}

# Ractor#inspect
# Return only id and status for main ractor
assert_equal "#<Ractor:#1 running>", %q{
  Ractor.current.inspect
}

# Return id, loc, and status for no-name ractor
assert_match /^#<Ractor:#([^ ]*?) .+:[0-9]+ terminated>$/, %q{
  r = Ractor.new { '' }
  r.take
  sleep 0.1 until r.inspect =~ /terminated/
  r.inspect
}

# Return id, name, loc, and status for named ractor
assert_match /^#<Ractor:#([^ ]*?) Test Ractor .+:[0-9]+ terminated>$/, %q{
  r = Ractor.new(name: 'Test Ractor') { '' }
  r.take
  sleep 0.1 until r.inspect =~ /terminated/
  r.inspect
}

# A return value of a Ractor block will be a message from the Ractor.
assert_equal 'ok', %q{
  # join
  r = Ractor.new do
    'ok'
  end
  r.take
}

# Passed arguments to Ractor.new will be a block parameter
# The values are passed with Ractor-communication pass.
assert_equal 'ok', %q{
  # ping-pong with arg
  r = Ractor.new 'ok' do |msg|
    msg
  end
  r.take
}

# Pass multiple arguments to Ractor.new
assert_equal 'ok', %q{
  # ping-pong with two args
  r =  Ractor.new 'ping', 'pong' do |msg, msg2|
    [msg, msg2]
  end
  'ok' if r.take == ['ping', 'pong']
}

# Ractor#send passes an object with copy to a Ractor
# and Ractor.receive in the Ractor block can receive the passed value.
assert_equal 'ok', %q{
  r = Ractor.new do
    msg = Ractor.receive
  end
  r.send 'ok'
  r.take
}

# Ractor#receive_if can filter the message
assert_equal '[2, 3, 1]', %q{
  r = Ractor.new Ractor.current do |main|
    main << 1
    main << 2
    main << 3
  end
  a = []
  a << Ractor.receive_if{|msg| msg == 2}
  a << Ractor.receive_if{|msg| msg == 3}
  a << Ractor.receive
}

# Ractor#receive_if with break
assert_equal '[2, [1, :break], 3]', %q{
  r = Ractor.new Ractor.current do |main|
    main << 1
    main << 2
    main << 3
  end

  a = []
  a << Ractor.receive_if{|msg| msg == 2}
  a << Ractor.receive_if{|msg| break [msg, :break]}
  a << Ractor.receive
}

# Ractor#receive_if can't be called recursively
assert_equal '[[:e1, 1], [:e2, 2]]', %q{
  r = Ractor.new Ractor.current do |main|
    main << 1
    main << 2
    main << 3
  end

  a = []

  Ractor.receive_if do |msg|
    begin
      Ractor.receive
    rescue Ractor::Error
      a << [:e1, msg]
    end
    true # delete 1 from queue
  end

  Ractor.receive_if do |msg|
    begin
      Ractor.receive_if{}
    rescue Ractor::Error
      a << [:e2, msg]
    end
    true # delete 2 from queue
  end

  a #
}

# dtoa race condition
assert_equal '[:ok, :ok, :ok]', %q{
  n = 3
  n.times.map{
    Ractor.new{
      10_000.times{ rand.to_s }
      :ok
    }
  }.map(&:take)
}

# Ractor.make_shareable issue for locals in proc [Bug #18023]
assert_equal '[:a, :b, :c, :d, :e]', %q{
  v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e
  closure = Ractor.current.instance_eval{ Proc.new { [v1, v2, v3, v4, v5] } }

  Ractor.make_shareable(closure).call
}

# Ractor.make_shareable issue for locals in proc [Bug #18023]
assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{
  a = :a
  closure = Ractor.current.instance_eval do
    -> {
      b, c, d = :b, :c, :d
      -> {
        e, f, g = :e, :f, :g
        -> { [a, b, c, d, e, f, g] }
      }.call
    }.call
  end

  Ractor.make_shareable(closure).call
}

# Now autoload in non-main Ractor is not supported
assert_equal 'ok', %q{
  autoload :Foo, 'foo.rb'
  r = Ractor.new do
    p Foo
  rescue Ractor::UnsafeError
    :ok
  end
  r.take
}

###
###
# Ractor still has several memory corruption so skip huge number of tests
if ENV['GITHUB_WORKFLOW'] &&
   ENV['GITHUB_WORKFLOW'] == 'Compilations'
   # ignore the follow
else

# Ractor.select(*ractors) receives a values from a ractors.
# It is similar to select(2) and Go's select syntax.
# The return value is [ch, received_value]
assert_equal 'ok', %q{
  # select 1
  r1 = Ractor.new{'r1'}
  r, obj = Ractor.select(r1)
  'ok' if r == r1 and obj == 'r1'
}

# Ractor.select from two ractors.
assert_equal '["r1", "r2"]', %q{
  # select 2
  r1 = Ractor.new{'r1'}
  r2 = Ractor.new{'r2'}
  rs = [r1, r2]
  as = []
  r, obj = Ractor.select(*rs)
  rs.delete(r)
  as << obj
  r, obj = Ractor.select(*rs)
  as << obj
  as.sort #=> ["r1", "r2"]
}

# Ractor.select from multiple ractors.
assert_equal 30.times.map { 'ok' }.to_s, %q{
  def test n
    rs = (1..n).map do |i|
      Ractor.new(i) do |i|
        "r#{i}"
      end
    end
    as = []
    all_rs = rs.dup

    n.times{
      r, obj = Ractor.select(*rs)
      as << [r, obj]
      rs.delete(r)
    }

    if as.map{|r, o| r.object_id}.sort == all_rs.map{|r| r.object_id}.sort &&
       as.map{|r, o| o}.sort == (1..n).map{|i| "r#{i}"}.sort
      'ok'
    else
      'ng'
    end
  end

  30.times.map{|i|
    test i
  }
} unless (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878

# Exception for empty select
assert_match /specify at least one ractor/, %q{
  begin
    Ractor.select
  rescue ArgumentError => e
    e.message
  end
}

# Outgoing port of a ractor will be closed when the Ractor is terminated.
assert_equal 'ok', %q{
  r = Ractor.new do
    'finish'
  end

  r.take
  sleep 0.1 until r.inspect =~ /terminated/

  begin
    o = r.take
  rescue Ractor::ClosedError
    'ok'
  else
    "ng: #{o}"
  end
}

# Raise Ractor::ClosedError when try to send into a terminated ractor
assert_equal 'ok', %q{
  r = Ractor.new do
  end

  r.take # closed
  sleep 0.1 until r.inspect =~ /terminated/

  begin
    r.send(1)
  rescue Ractor::ClosedError
    'ok'
  else
    'ng'
  end
}

# Raise Ractor::ClosedError when try to send into a closed actor
assert_equal 'ok', %q{
  r = Ractor.new { Ractor.receive }
  r.close_incoming

  begin
    r.send(1)
  rescue Ractor::ClosedError
    'ok'
  else
    'ng'
  end
}

# Raise Ractor::ClosedError when try to take from closed actor
assert_equal 'ok', %q{
  r = Ractor.new do
    Ractor.yield 1
    Ractor.receive
  end

  r.close_outgoing
  begin
    r.take
  rescue Ractor::ClosedError
    'ok'
  else
    'ng'
  end
}

# Can mix with Thread#interrupt and Ractor#take [Bug #17366]
assert_equal 'err', %q{
  Ractor.new{
    t = Thread.current
    begin
      Thread.new{ t.raise "err" }.join
    rescue => e
      e.message
    end
  }.take
}

# Killed Ractor's thread yields nil
assert_equal 'nil', %q{
  Ractor.new{
    t = Thread.current
    Thread.new{ t.kill }.join
  }.take.inspect #=> nil
}

# Ractor.yield raises Ractor::ClosedError when outgoing port is closed.
assert_equal 'ok', %q{
  r = Ractor.new Ractor.current do |main|
    Ractor.receive
    main << true
    Ractor.yield 1
  end

  r.close_outgoing
  r << true
  Ractor.receive

  begin
    r.take
  rescue Ractor::ClosedError
    'ok'
  else
    'ng'
  end
}

# Raise Ractor::ClosedError when try to send into a ractor with closed incoming port
assert_equal 'ok', %q{
  r = Ractor.new { Ractor.receive }
  r.close_incoming

  begin
    r.send(1)
  rescue Ractor::ClosedError
    'ok'
  else
    'ng'
  end
}

# A ractor with closed incoming port still can send messages out
assert_equal '[1, 2]', %q{
  r = Ractor.new do
    Ractor.yield 1
    2
  end
  r.close_incoming

  [r.take, r.take]
}

# Raise Ractor::ClosedError when try to take from a ractor with closed outgoing port
assert_equal 'ok', %q{
  r = Ractor.new do
    Ractor.yield 1
    Ractor.receive
  end

  sleep 0.01 # wait for Ractor.yield in r
  r.close_outgoing
  begin
    r.take
  rescue Ractor::ClosedError
    'ok'
  else
    'ng'
  end
}

# A ractor with closed outgoing port still can receive messages from incoming port
assert_equal 'ok', %q{
  r = Ractor.new do
    Ractor.receive
  end

  r.close_outgoing
  begin
    r.send(1)
  rescue Ractor::ClosedError
    'ng'
  else
    'ok'
  end
}

# Ractor.main returns main ractor
assert_equal 'true', %q{
  Ractor.new{
    Ractor.main
  }.take == Ractor.current
}

# a ractor with closed outgoing port should terminate
assert_equal 'ok', %q{
  Ractor.new do
    close_outgoing
  end

  true until Ractor.count == 1
  :ok
}

# multiple Ractors can receive (wait) from one Ractor
assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{
  pipe = Ractor.new do
    loop do
      Ractor.yield Ractor.receive
    end
  end

  RN = 10
  rs = RN.times.map{|i|
    Ractor.new pipe, i do |pipe, i|
      msg = pipe.take
      msg # ping-pong
    end
  }
  RN.times{|i|
    pipe << i
  }
  RN.times.map{
    r, n = Ractor.select(*rs)
    rs.delete r
    n
  }.sort
} unless /mswin/ =~ RUBY_PLATFORM # randomly hangs on mswin https://github.com/ruby/ruby/actions/runs/3753871445/jobs/6377551069#step:20:131

# Ractor.select also support multiple take, receive and yield
assert_equal '[true, true, true]', %q{
  RN = 10
  CR = Ractor.current

  rs = (1..RN).map{
    Ractor.new do
      CR.send 'send' + CR.take #=> 'sendyield'
      'take'
    end
  }
  received = []
  taken = []
  yielded = []
  until received.size == RN && taken.size == RN && yielded.size == RN
    r, v = Ractor.select(CR, *rs, yield_value: 'yield')
    case r
    when :receive
      received << v
    when :yield
      yielded << v
    else
      taken << v
      rs.delete r
    end
  end
  r = [received == ['sendyield'] * RN,
       yielded  == [nil] * RN,
       taken    == ['take'] * RN,
  ]

  STDERR.puts [received, yielded, taken].inspect
  r
}

# multiple Ractors can send to one Ractor
assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{
  pipe = Ractor.new do
    loop do
      Ractor.yield Ractor.receive
    end
  end

  RN = 10
  RN.times.map{|i|
    Ractor.new pipe, i do |pipe, i|
      pipe << i
    end
  }
  RN.times.map{
    pipe.take
  }.sort
}

# an exception in a Ractor will be re-raised at Ractor#receive
assert_equal '[RuntimeError, "ok", true]', %q{
  r = Ractor.new do
    raise 'ok' # exception will be transferred receiver
  end
  begin
    r.take
  rescue Ractor::RemoteError => e
    [e.cause.class,   #=> RuntimeError
     e.cause.message, #=> 'ok'
     e.ractor == r]   #=> true
  end
}

# threads in a ractor will killed
assert_equal '{:ok=>3}', %q{
  Ractor.new Ractor.current do |main|
    q = Thread::Queue.new
    Thread.new do
      q << true
      loop{}
    ensure
      main << :ok
    end

    Thread.new do
      q << true
      while true
      end
    ensure
      main << :ok
    end

    Thread.new do
      q << true
      sleep 1
    ensure
      main << :ok
    end

    # wait for the start of all threads
    3.times{q.pop}
  end

  3.times.map{Ractor.receive}.tally
}

# unshareable object are copied
assert_equal 'false', %q{
  obj = 'str'.dup
  r = Ractor.new obj do |msg|
    msg.object_id
  end

  obj.object_id == r.take
}

# To copy the object, now Marshal#dump is used
assert_equal "allocator undefined for Thread", %q{
  obj = Thread.new{}
  begin
    r = Ractor.new obj do |msg|
      msg
    end
  rescue TypeError => e
    e.message #=> no _dump_data is defined for class Thread
  else
    'ng'
  end
}

# send shareable and unshareable objects
assert_equal "ok", %q{
  echo_ractor = Ractor.new do
    loop do
      v = Ractor.receive
      Ractor.yield v
    end
  end

  class C; end
  module M; end
  S = Struct.new(:a, :b, :c, :d)

  shareable_objects = [
    true,
    false,
    nil,
    1,
    1.1,    # Float
    1+2r,   # Rational
    3+4i,   # Complex
    2**128, # Bignum
    :sym,   # Symbol
    'xyzzy'.to_sym, # dynamic symbol
    'frozen'.freeze, # frozen String
    /regexp/, # regexp literal
    /reg{true}exp/.freeze, # frozen dregexp
    [1, 2].freeze,   # frozen Array which only refers to shareable
    {a: 1}.freeze,   # frozen Hash which only refers to shareable
    [{a: 1}.freeze, 'str'.freeze].freeze, # nested frozen container
    S.new(1, 2).freeze, # frozen Struct
    S.new(1, 2, 3, 4).freeze, # frozen Struct
    (1..2), # Range on Struct
    (1..),  # Range on Struct
    (..1),  # Range on Struct
    C, # class
    M, # module
    Ractor.current, # Ractor
  ]

  unshareable_objects = [
    'mutable str'.dup,
    [:array],
    {hash: true},
    S.new(1, 2),
    S.new(1, 2, 3, 4),
    S.new("a", 2).freeze, # frozen, but refers to an unshareable object
  ]

  results = []

  shareable_objects.map{|o|
    echo_ractor << o
    o2 = echo_ractor.take
    results << "#{o} is copied" unless o.object_id == o2.object_id
  }

  unshareable_objects.map{|o|
    echo_ractor << o
    o2 = echo_ractor.take
    results << "#{o.inspect} is not copied" if o.object_id == o2.object_id
  }

  if results.empty?
    :ok
  else
    results.inspect
  end
}

# frozen Objects are shareable
assert_equal [false, true, false].inspect, %q{
  class C
    def initialize freeze
      @a = 1
      @b = :sym
      @c = 'frozen_str'
      @c.freeze if freeze
      @d = true
    end
  end

  def check obj1
    obj2 = Ractor.new obj1 do |obj|
      obj
    end.take

    obj1.object_id == obj2.object_id
  end

  results = []
  results << check(C.new(true))         # false
  results << check(C.new(true).freeze)  # true
  results << check(C.new(false).freeze) # false
}

# move example2: String
# touching moved object causes an error
assert_equal 'hello world', %q{
  # move
  r = Ractor.new do
    obj = Ractor.receive
    obj << ' world'
  end

  str = 'hello'
  r.send str, move: true
  modified = r.take

  begin
    str << ' exception' # raise Ractor::MovedError
  rescue Ractor::MovedError
    modified #=> 'hello world'
  else
    raise 'unreachable'
  end
}

# move example2: Array
assert_equal '[0, 1]', %q{
  r = Ractor.new do
    ary = Ractor.receive
    ary << 1
  end

  a1 = [0]
  r.send a1, move: true
  a2 = r.take
  begin
    a1 << 2 # raise Ractor::MovedError
  rescue Ractor::MovedError
    a2.inspect
  end
}

# move with yield
assert_equal 'hello', %q{
  r = Ractor.new do
    Thread.current.report_on_exception = false
    obj = 'hello'
    Ractor.yield obj, move: true
    obj << 'world'
  end

  str = r.take
  begin
    r.take
  rescue Ractor::RemoteError
    str #=> "hello"
  end
}

# yield/move should not make moved object when the yield is not succeeded
assert_equal '"str"', %q{
  R = Ractor.new{}
  M = Ractor.current
  r = Ractor.new do
    s = 'str'
    selected_r, v = Ractor.select R, yield_value: s, move: true
    raise if selected_r != R # taken from R
    M.send s.inspect # s should not be a moved object
  end

  Ractor.receive
}

# yield/move can fail
assert_equal "allocator undefined for Thread", %q{
  r = Ractor.new do
    obj = Thread.new{}
    Ractor.yield obj
  rescue => e
    e.message
  end
  r.take
}

# Access to global-variables are prohibited
assert_equal 'can not access global variables $gv from non-main Ractors', %q{
  $gv = 1
  r = Ractor.new do
    $gv
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# Access to global-variables are prohibited
assert_equal 'can not access global variables $gv from non-main Ractors', %q{
  r = Ractor.new do
    $gv = 1
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# $stdin,out,err is Ractor local, but shared fds
assert_equal 'ok', %q{
  r = Ractor.new do
    [$stdin, $stdout, $stderr].map{|io|
      [io.object_id, io.fileno]
    }
  end

  [$stdin, $stdout, $stderr].zip(r.take){|io, (oid, fno)|
    raise "should not be different object" if io.object_id == oid
    raise "fd should be same" unless io.fileno == fno
  }
  'ok'
}

# $stdin,out,err belong to Ractor
assert_equal 'ok', %q{
  r = Ractor.new do
    $stdin.itself
    $stdout.itself
    $stderr.itself
    'ok'
  end

  r.take
}

# $DEBUG, $VERBOSE are Ractor local
assert_equal 'true', %q{
  $DEBUG = true
  $VERBOSE = true

  def ractor_local_globals
    /a(b)(c)d/ =~ 'abcd' # for $~
    `echo foo` unless  /solaris/ =~ RUBY_PLATFORM

    {
     # ractor-local (derived from created ractor): debug
     '$DEBUG' => $DEBUG,
     '$-d' => $-d,

     # ractor-local (derived from created ractor): verbose
     '$VERBOSE' => $VERBOSE,
     '$-w' => $-w,
     '$-W' => $-W,
     '$-v' => $-v,

     # process-local (readonly): other commandline parameters
     '$-p' => $-p,
     '$-l' => $-l,
     '$-a' => $-a,

     # process-local (readonly): getpid
     '$$'  => $$,

     # thread local: process result
     '$?'  => $?,

     # scope local: match
     '$~'  => $~.inspect,
     '$&'  => $&,
     '$`'  => $`,
     '$\''  => $',
     '$+'  => $+,
     '$1'  => $1,

     # scope local: last line
     '$_' => $_,

     # scope local: last backtrace
     '$@' => $@,
     '$!' => $!,

     # ractor local: stdin, out, err
     '$stdin'  => $stdin.inspect,
     '$stdout' => $stdout.inspect,
     '$stderr' => $stderr.inspect,
    }
  end

  h = Ractor.new do
    ractor_local_globals
  end.take
  ractor_local_globals == h #=> true
}

# selfs are different objects
assert_equal 'false', %q{
  r = Ractor.new do
    self.object_id
  end
  r.take == self.object_id #=> false
}

# self is a Ractor instance
assert_equal 'true', %q{
  r = Ractor.new do
    self.object_id
  end
  r.object_id == r.take #=> true
}

# given block Proc will be isolated, so can not access outer variables.
assert_equal 'ArgumentError', %q{
  begin
    a = true
    r = Ractor.new do
      a
    end
  rescue => e
    e.class
  end
}

# ivar in shareable-objects are not allowed to access from non-main Ractor
assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", %q{
  class C
    @iv = 'str'
  end

  r = Ractor.new do
    class C
      p @iv
    end
  end


  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# ivar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
  shared = Ractor.new{}
  shared.instance_variable_set(:@iv, 'str')

  r = Ractor.new shared do |shared|
    p shared.instance_variable_get(:@iv)
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (get)
assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
  class Ractor
    def setup
      @foo = ''
    end

    def foo
      @foo
    end
  end

  shared = Ractor.new{}
  shared.setup

  r = Ractor.new shared do |shared|
    p shared.foo
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (set)
assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
  class Ractor
    def setup
      @foo = ''
    end
  end

  shared = Ractor.new{}

  r = Ractor.new shared do |shared|
    p shared.setup
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# But a shareable object is frozen, it is allowed to access ivars from non-main Ractor
assert_equal '11', %q{
  [Object.new, [], ].map{|obj|
    obj.instance_variable_set('@a', 1)
    Ractor.make_shareable obj = obj.freeze

    Ractor.new obj do |obj|
      obj.instance_variable_get('@a')
    end.take.to_s
  }.join
}

# and instance variables of classes/modules are accessible if they refer shareable objects
assert_equal '333', %q{
  class C
    @int = 1
    @str = '-1000'.dup
    @fstr = '100'.freeze

    def self.int = @int
    def self.str = @str
    def self.fstr = @fstr
  end

  module M
    @int = 2
    @str = '-2000'.dup
    @fstr = '200'.freeze

    def self.int = @int
    def self.str = @str
    def self.fstr = @fstr
  end

  a = Ractor.new{ C.int }.take
  b = Ractor.new do
    C.str.to_i
  rescue Ractor::IsolationError
    10
  end.take
  c = Ractor.new do
    C.fstr.to_i
  end.take

  d = Ractor.new{ M.int }.take
  e = Ractor.new do
    M.str.to_i
  rescue Ractor::IsolationError
    20
  end.take
  f = Ractor.new do
    M.fstr.to_i
  end.take


  # 1 + 10 + 100 + 2 + 20 + 200
  a + b + c + d + e + f
}

# moved objects have their shape properly set to original object's shape
assert_equal '1234', %q{
class Obj
  attr_accessor :a, :b, :c, :d
  def initialize
    @a = 1
    @b = 2
    @c = 3
  end
end
r = Ractor.new do
  obj = receive
  obj.d = 4
  [obj.a, obj.b, obj.c, obj.d]
end
obj = Obj.new
r.send(obj, move: true)
values = r.take
values.join
}

# cvar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access class variables from non-main Ractors', %q{
  class C
    @@cv = 'str'
  end

  r = Ractor.new do
    class C
      p @@cv
    end
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# also cached cvar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access class variables from non-main Ractors', %q{
  class C
    @@cv = 'str'
    def self.cv
      @@cv
    end
  end

  C.cv # cache

  r = Ractor.new do
    C.cv
  end

  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# Getting non-shareable objects via constants by other Ractors is not allowed
assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', %q{
  class C
    CONST = 'str'
  end
  r = Ractor.new do
    C::CONST
  end
  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# Constant cache should care about non-sharable constants
assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", %q{
  STR = "hello"
  def str; STR; end
  s = str() # fill const cache
  begin
    Ractor.new{ str() }.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# Setting non-shareable objects into constants by other Ractors is not allowed
assert_equal 'can not set constants with non-shareable objects by non-main Ractors', %q{
  class C
  end
  r = Ractor.new do
    C::CONST = 'str'
  end
  begin
    r.take
  rescue Ractor::RemoteError => e
    e.cause.message
  end
}

# define_method is not allowed
assert_equal "defined with an un-shareable Proc in a different Ractor", %q{
  str = "foo"
  define_method(:buggy){|i| str << "#{i}"}
  begin
    Ractor.new{buggy(10)}.take
  rescue => e
    e.cause.message
  end
}

# Immutable Array and Hash are shareable, so it can be shared with constants
assert_equal '[1000, 3]', %q{
  A = Array.new(1000).freeze # [nil, ...]
  H = {a: 1, b: 2, c: 3}.freeze

  Ractor.new{ [A.size, H.size] }.take
}

# Ractor.count
assert_equal '[1, 4, 3, 2, 1]', %q{
  counts = []
  counts << Ractor.count
  ractors = (1..3).map { Ractor.new { Ractor.receive } }
  counts << Ractor.count

  ractors[0].send('End 0').take
  sleep 0.1 until ractors[0].inspect =~ /terminated/
  counts << Ractor.count

  ractors[1].send('End 1').take
  sleep 0.1 until ractors[1].inspect =~ /terminated/
  counts << Ractor.count

  ractors[2].send('End 2').take
  sleep 0.1 until ractors[2].inspect =~ /terminated/
  counts << Ractor.count

  counts.inspect
}

# ObjectSpace.each_object can not handle unshareable objects with Ractors
assert_equal '0', %q{
  Ractor.new{
    n = 0
    ObjectSpace.each_object{|o| n += 1 unless Ractor.shareable?(o)}
    n
  }.take
}

# ObjectSpace._id2ref can not handle unshareable objects with Ractors
assert_equal 'ok', %q{
  s = 'hello'

  Ractor.new s.object_id do |id ;s|
    begin
      s = ObjectSpace._id2ref(id)
    rescue => e
      :ok
    end
  end.take
}

# Ractor.make_shareable(obj)
assert_equal 'true', %q{
  class C
    def initialize
      @a = 'foo'
      @b = 'bar'
    end

    def freeze
      @c = [:freeze_called]
      super
    end

    attr_reader :a, :b, :c
  end
  S = Struct.new(:s1, :s2)
  str = "hello"
  str.instance_variable_set("@iv", "hello")
  /a/ =~ 'a'
  m = $~
  class N < Numeric
    def /(other)
      1
    end
  end
  ary = []; ary << ary

  a = [[1, ['2', '3']],
       {Object.new => "hello"},
       C.new,
       S.new("x", "y"),
       ("a".."b"),
       str,
       ary,             # cycle
       /regexp/,
       /#{'r'.upcase}/,
       m,
       Complex(N.new,0),
       Rational(N.new,0),
       true,
       false,
       nil,
       1, 1.2, 1+3r, 1+4i, # Numeric
  ]
  Ractor.make_shareable(a)

  # check all frozen
  a.each{|o|
    raise o.inspect unless o.frozen?

    case o
    when C
      raise o.a.inspect unless o.a.frozen?
      raise o.b.inspect unless o.b.frozen?
      raise o.c.inspect unless o.c.frozen? && o.c == [:freeze_called]
    when Rational
      raise o.numerator.inspect unless o.numerator.frozen?
    when Complex
      raise o.real.inspect unless o.real.frozen?
    when Array
      if o[0] == 1
        raise o[1][1].inspect unless o[1][1].frozen?
      end
    when Hash
      o.each{|k, v|
        raise k.inspect unless k.frozen?
        raise v.inspect unless v.frozen?
      }
    end
  }

  Ractor.shareable?(a)
}

# Ractor.make_shareable(obj) doesn't freeze shareable objects
assert_equal 'true', %q{
  r = Ractor.new{}
  Ractor.make_shareable(a = [r])
  [a.frozen?, a[0].frozen?] == [true, false]
}

# Ractor.make_shareable(a_proc) makes a proc shareable.
assert_equal 'true', %q{
  a = [1, [2, 3], {a: "4"}]

  pr = Ractor.current.instance_eval do
    Proc.new do
      a
    end
  end

  Ractor.make_shareable(a) # referred value should be shareable
  Ractor.make_shareable(pr)
  Ractor.shareable?(pr)
}

# Ractor.shareable?(recursive_objects)
assert_equal '[false, false]', %q{
  y = []
  x = [y, {}].freeze
  y << x
  y.freeze
  [Ractor.shareable?(x), Ractor.shareable?(y)]
}

# Ractor.make_shareable(recursive_objects)
assert_equal '[:ok, false, false]', %q{
  o = Object.new
  def o.freeze; raise; end
  y = []
  x = [y, o].freeze
  y << x
  y.freeze
  [(Ractor.make_shareable(x) rescue :ok), Ractor.shareable?(x), Ractor.shareable?(y)]
}

# Ractor.make_shareable with Class/Module
assert_equal '[C, M]', %q{
  class C; end
  module M; end

  Ractor.make_shareable(ary = [C, M])
}

# define_method() can invoke different Ractor's proc if the proc is shareable.
assert_equal '1', %q{
  class C
    a = 1
    define_method "foo", Ractor.make_shareable(Proc.new{ a })
    a = 2
  end

  Ractor.new{ C.new.foo }.take
}

# Ractor.make_shareable(a_proc) makes a proc shareable.
assert_equal 'can not make a Proc shareable because it accesses outer variables (a).', %q{
  a = b = nil
  pr = Ractor.current.instance_eval do
    Proc.new do
      c = b # assign to a is okay because c is block local variable
      # reading b is okay
      a = b # assign to a is not allowed #=> Ractor::Error
    end
  end

  begin
    Ractor.make_shareable(pr)
  rescue => e
    e.message
  end
}

# Ractor.make_shareable(obj, copy: true) makes copied shareable object.
assert_equal '[false, false, true, true]', %q{
  r = []
  o1 = [1, 2, ["3"]]

  o2 = Ractor.make_shareable(o1, copy: true)
  r << Ractor.shareable?(o1) # false
  r << (o1.object_id == o2.object_id) # false

  o3 = Ractor.make_shareable(o1)
  r << Ractor.shareable?(o1) # true
  r << (o1.object_id == o3.object_id) # false
  r
}

# TracePoint with normal Proc should be Ractor local
assert_equal '[6, 10]', %q{
  rs = []
  TracePoint.new(:line){|tp| rs << tp.lineno if tp.path == __FILE__}.enable do
    Ractor.new{ # line 4
      a = 1
      b = 2
    }.take
    c = 3       # line 8
  end
  rs
}

# Ractor deep copies frozen objects (ary)
assert_equal '[true, false]', %q{
  Ractor.new([[]].freeze) { |ary|
    [ary.frozen?, ary.first.frozen? ]
  }.take
}

# Ractor deep copies frozen objects (str)
assert_equal '[true, false]', %q{
  s = String.new.instance_eval { @x = []; freeze}
  Ractor.new(s) { |s|
    [s.frozen?, s.instance_variable_get(:@x).frozen?]
  }.take
}

# Can not trap with not isolated Proc on non-main ractor
assert_equal '[:ok, :ok]', %q{
  a = []
  Ractor.new{
    trap(:INT){p :ok}
  }.take
  a << :ok

  begin
    Ractor.new{
      s = 'str'
      trap(:INT){p s}
    }.take
  rescue => Ractor::RemoteError
    a << :ok
  end
}

# Ractor-local storage
assert_equal '[nil, "b", "a"]', %q{
  ans = []
  Ractor.current[:key] = 'a'
  r = Ractor.new{
    Ractor.yield self[:key]
    self[:key] = 'b'
    self[:key]
  }
  ans << r.take
  ans << r.take
  ans << Ractor.current[:key]
}

###
### Synchronization tests
###

N = 100_000

# fstring pool
assert_equal "#{N}#{N}", %Q{
  N = #{N}
  2.times.map{
    Ractor.new{
      N.times{|i| -(i.to_s)}
    }
  }.map{|r| r.take}.join
}

# Generic ivtbl
n = N/2
assert_equal "#{n}#{n}", %Q{
  2.times.map{
    Ractor.new do
      #{n}.times do
        obj = ''
        obj.instance_variable_set("@a", 1)
        obj.instance_variable_set("@b", 1)
        obj.instance_variable_set("@c", 1)
        obj.instance_variable_defined?("@a")
      end
    end
  }.map{|r| r.take}.join
}

# NameError
assert_equal "ok", %q{
  obj = "".freeze # NameError refers the receiver indirectly
  begin
    obj.bar
  rescue => err
  end
  begin
    Ractor.new{} << err
  rescue TypeError
    'ok'
  end
}

assert_equal "ok", %q{
  GC.disable
  Ractor.new {}
  raise "not ok" unless GC.disable

  foo = []
  10.times { foo << 1 }

  GC.start

  'ok'
}

# Can yield back values while GC is sweeping [Bug #18117]
assert_equal "ok", %q{
  workers = (0...8).map do
    Ractor.new do
      loop do
        10_000.times.map { Object.new }
        Ractor.yield Time.now
      end
    end
  end

  1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) }
  "ok"
} unless yjit_enabled? || rjit_enabled? # flaky

assert_equal "ok", %q{
  def foo(*); ->{ super }; end
  begin
    Ractor.make_shareable(foo)
  rescue Ractor::IsolationError
    "ok"
  end
}

assert_equal "ok", %q{
  def foo(**); ->{ super }; end
  begin
    Ractor.make_shareable(foo)
  rescue Ractor::IsolationError
    "ok"
  end
}

assert_equal "ok", %q{
  def foo(...); ->{ super }; end
  begin
    Ractor.make_shareable(foo)
  rescue Ractor::IsolationError
    "ok"
  end
}

assert_equal "ok", %q{
  def foo((x), (y)); ->{ super }; end
  begin
    Ractor.make_shareable(foo([], []))
  rescue Ractor::IsolationError
    "ok"
  end
}

# check method cache invalidation
assert_equal "ok", %q{
  module M
    def foo
      @foo
    end
  end

  class A
    include M

    def initialize
      100.times { |i| instance_variable_set(:"@var_#{i}", "bad: #{i}") }
      @foo = 2
    end
  end

  class B
    include M

    def initialize
      @foo = 1
    end
  end

  Ractor.new do
    b = B.new
    100_000.times do
      raise unless b.foo == 1
    end
  end

  a = A.new
  100_000.times do
    raise unless a.foo == 2
  end

  "ok"
}

# check method cache invalidation
assert_equal 'true', %q{
  class C1; def self.foo = 1; end
  class C2; def self.foo = 2; end
  class C3; def self.foo = 3; end
  class C4; def self.foo = 5; end
  class C5; def self.foo = 7; end
  class C6; def self.foo = 11; end
  class C7; def self.foo = 13; end
  class C8; def self.foo = 17; end

  LN = 10_000
  RN = 10
  CS = [C1, C2, C3, C4, C5, C6, C7, C8]
  rs = RN.times.map{|i|
    Ractor.new(CS.shuffle){|cs|
      LN.times.sum{
        cs.inject(1){|r, c| r * c.foo} # c.foo invalidates method cache entry
      }
    }
  }

  n = CS.inject(1){|r, c| r * c.foo} * LN
  rs.map{|r| r.take} == Array.new(RN){n}
}

# check experimental warning
assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{
  Warning[:experimental] = $VERBOSE = true
  STDERR.reopen(STDOUT)
  eval("Ractor.new{}.take", nil, "test_ractor.rb", 1)
}

# check moved object
assert_equal 'ok', %q{
  r = Ractor.new do
    Ractor.receive
    GC.start
    :ok
  end

  obj = begin
  raise
  rescue => e
    e = Marshal.load(Marshal.dump(e))
  end

  r.send obj, move: true
  r.take
}

## Ractor::Selector

# Selector#empty? returns true
assert_equal 'true', %q{
  skip true unless defined? Ractor::Selector

  s = Ractor::Selector.new
  s.empty?
}

# Selector#empty? returns false if there is target ractors
assert_equal 'false', %q{
  skip false unless defined? Ractor::Selector

  s = Ractor::Selector.new
  s.add Ractor.new{}
  s.empty?
}

# Selector#clear removes all ractors from the waiting list
assert_equal 'true', %q{
  skip true unless defined? Ractor::Selector

  s = Ractor::Selector.new
  s.add Ractor.new{10}
  s.add Ractor.new{20}
  s.clear
  s.empty?
}

# Selector#wait can wait multiple ractors
assert_equal '[10, 20, true]', %q{
  skip [10, 20, true] unless defined? Ractor::Selector

  s = Ractor::Selector.new
  s.add Ractor.new{10}
  s.add Ractor.new{20}
  r, v = s.wait
  vs = []
  vs << v
  r, v = s.wait
  vs << v
  [*vs.sort, s.empty?]
} if defined? Ractor::Selector

# Selector#wait can wait multiple ractors with receiving.
assert_equal '30', %q{
  skip 30 unless defined? Ractor::Selector

  RN = 30
  rs = RN.times.map{
    Ractor.new{ :v }
  }
  s = Ractor::Selector.new(*rs)

  results = []
  until s.empty?
    results << s.wait

    # Note that s.wait can raise an exception because other Ractors/Threads
    # can take from the same ractors in the waiting set.
    # In this case there is no other takers so `s.wait` doesn't raise an error.
  end

  results.size
} if defined? Ractor::Selector

# Selector#wait can support dynamic addition
assert_equal '600', %q{
  skip 600 unless defined? Ractor::Selector

  RN = 100
  s = Ractor::Selector.new
  rs = RN.times.map{
    Ractor.new{
      Ractor.main << Ractor.new{ Ractor.yield :v3; :v4 }
      Ractor.main << Ractor.new{ Ractor.yield :v5; :v6 }
      Ractor.yield :v1
      :v2
    }
  }

  rs.each{|r| s.add(r)}
  h = {v1: 0, v2: 0, v3: 0, v4: 0, v5: 0, v6: 0}

  loop do
    case s.wait receive: true
    in :receive, r
      s.add r
    in r, v
      h[v] += 1
      break if h.all?{|k, v| v == RN}
    end
  end

  h.sum{|k, v| v}
} unless yjit_enabled? # http://ci.rvm.jp/results/trunk-yjit@ruby-sp2-docker/4466770

# Selector should be GCed (free'ed) without trouble
assert_equal 'ok', %q{
  skip :ok unless defined? Ractor::Selector

  RN = 30
  rs = RN.times.map{
    Ractor.new{ :v }
  }
  s = Ractor::Selector.new(*rs)
  :ok
}

end # if !ENV['GITHUB_WORKFLOW']
