# -*- coding: us-ascii -*-
# frozen_string_literal: false
require 'test/unit'
require "rbconfig/sizeof"
require "timeout"

class TestThread < Test::Unit::TestCase
  class Thread < ::Thread
    Threads = []
    def self.new(*)
      th = super
      Threads << th
      th
    end
  end

  def setup
    Thread::Threads.clear
  end

  def teardown
    Thread::Threads.each do |t|
      t.kill if t.alive?
      begin
        t.join
      rescue Exception
      end
    end
  end

  def test_inspect
    line = __LINE__+1
    th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start{}
    s = th.inspect
    assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:")
    assert_include(s, " #{__FILE__}:#{line} ")
    assert_equal(s, th.to_s)
  ensure
    th.join
  end

  def test_inspect_with_fiber
    inspect1 = inspect2 = nil

    Thread.new{
      inspect1 = Thread.current.inspect
      Fiber.new{
        inspect2 = Thread.current.inspect
      }.resume
    }.join

    assert_equal inspect1, inspect2, '[Bug #13689]'
  end

  def test_main_thread_variable_in_enumerator
    assert_equal Thread.main, Thread.current

    Thread.current.thread_variable_set :foo, "bar"

    thread, value = Fiber.new {
      Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
    }.resume

    assert_equal Thread.current, thread
    assert_equal Thread.current.thread_variable_get(:foo), value
  end

  def test_thread_variable_in_enumerator
    Thread.new {
      Thread.current.thread_variable_set :foo, "bar"

      thread, value = Fiber.new {
        Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
      }.resume

      assert_equal Thread.current, thread
      assert_equal Thread.current.thread_variable_get(:foo), value
    }.join
  end

  def test_thread_variables
    assert_equal [], Thread.new { Thread.current.thread_variables }.join.value

    t = Thread.new {
      Thread.current.thread_variable_set(:foo, "bar")
      Thread.current.thread_variables
    }
    assert_equal [:foo], t.join.value
  end

  def test_thread_variable?
    Thread.new { assert_not_send([Thread.current, :thread_variable?, "foo"]) }.value
    t = Thread.new {
      Thread.current.thread_variable_set("foo", "bar")
    }.join

    assert_send([t, :thread_variable?, "foo"])
    assert_send([t, :thread_variable?, :foo])
    assert_not_send([t, :thread_variable?, :bar])
  end

  def test_thread_variable_strings_and_symbols_are_the_same_key
    t = Thread.new {}.join
    t.thread_variable_set("foo", "bar")
    assert_equal "bar", t.thread_variable_get(:foo)
  end

  def test_thread_variable_frozen
    t = Thread.new { }.join
    t.freeze
    assert_raise(FrozenError) do
      t.thread_variable_set(:foo, "bar")
    end
  end

  def test_mutex_synchronize
    m = Thread::Mutex.new
    r = 0
    num_threads = 10
    loop=100
    (1..num_threads).map{
      Thread.new{
        loop.times{
          m.synchronize{
            tmp = r
            # empty and waste loop for making thread preemption
            100.times {
            }
            r = tmp + 1
          }
        }
      }
    }.each{|e|
      e.join
    }
    assert_equal(num_threads*loop, r)
  end

  def test_mutex_synchronize_yields_no_block_params
    bug8097 = '[ruby-core:53424] [Bug #8097]'
    assert_empty(Thread::Mutex.new.synchronize {|*params| break params}, bug8097)
  end

  def test_local_barrier
    dir = File.dirname(__FILE__)
    lbtest = File.join(dir, "lbtest.rb")
    $:.unshift File.join(File.dirname(dir), 'ruby')
    $:.shift
    3.times {
      `#{EnvUtil.rubybin} #{lbtest}`
      assert_not_predicate($?, :coredump?, '[ruby-dev:30653]')
    }
  end

  def test_priority
    c1 = c2 = 0
    run = true
    t1 = Thread.new { c1 += 1 while run }
    t1.priority = 3
    t2 = Thread.new { c2 += 1 while run }
    t2.priority = -3
    assert_equal(3, t1.priority)
    assert_equal(-3, t2.priority)
    sleep 0.5
    5.times do
      assert_not_predicate(t1, :stop?)
      assert_not_predicate(t2, :stop?)
      break if c1 > c2
      sleep 0.1
    end
    run = false
    t1.kill
    t2.kill
    assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed
    t1.join
    t2.join
  end

  def test_new
    assert_raise(ThreadError) do
      Thread.new
    end

    t1 = Thread.new { sleep }
    assert_raise(ThreadError) do
      t1.instance_eval { initialize { } }
    end

    t2 = Thread.new(&method(:sleep).to_proc)
    assert_raise(ThreadError) do
      t2.instance_eval { initialize { } }
    end

  ensure
    t1&.kill&.join
    t2&.kill&.join
  end

  def test_new_symbol_proc
    bug = '[ruby-core:80147] [Bug #13313]'
    assert_ruby_status([], "#{<<-"begin;"}\n#{<<-'end;'}", bug)
    begin;
      exit("1" == Thread.start(1, &:to_s).value)
    end;
  end

  def test_join
    t = Thread.new { sleep }
    assert_nil(t.join(0.05))

  ensure
    t&.kill&.join
  end

  def test_join2
    ok = false
    t1 = Thread.new { ok = true; sleep }
    Thread.pass until ok
    Thread.pass until t1.stop?
    t2 = Thread.new do
      Thread.pass while ok
      t1.join(0.01)
    end
    t3 = Thread.new do
      ok = false
      t1.join
    end
    assert_nil(t2.value)
    t1.wakeup
    assert_equal(t1, t3.value)

  ensure
    t1&.kill&.join
    t2&.kill&.join
    t3&.kill&.join
  end

  { 'FIXNUM_MAX' => RbConfig::LIMITS['FIXNUM_MAX'],
    'UINT64_MAX' => RbConfig::LIMITS['UINT64_MAX'],
    'INFINITY'   => Float::INFINITY
  }.each do |name, limit|
    define_method("test_join_limit_#{name}") do
      t = Thread.new {}
      assert_same t, t.join(limit), "limit=#{limit.inspect}"
    end
  end

  { 'minus_1'        => -1,
    'minus_0_1'      => -0.1,
    'FIXNUM_MIN'     => RbConfig::LIMITS['FIXNUM_MIN'],
    'INT64_MIN'      => RbConfig::LIMITS['INT64_MIN'],
    'minus_INFINITY' => -Float::INFINITY
  }.each do |name, limit|
    define_method("test_join_limit_negative_#{name}") do
      t = Thread.new { sleep }
      begin
        assert_nothing_raised(Timeout::Error) do
          Timeout.timeout(30) do
            assert_nil t.join(limit), "limit=#{limit.inspect}"
          end
        end
      ensure
        t.kill
      end
    end
  end

  def test_kill_main_thread
    assert_in_out_err([], <<-INPUT, %w(1), [])
      p 1
      Thread.kill Thread.current
      p 2
    INPUT
  end

  def test_kill_wrong_argument
    bug4367 = '[ruby-core:35086]'
    assert_raise(TypeError, bug4367) {
      Thread.kill(nil)
    }
    o = Object.new
    assert_raise(TypeError, bug4367) {
      Thread.kill(o)
    }
  end

  def test_kill_thread_subclass
    c = Class.new(Thread)
    t = c.new { sleep 10 }
    assert_nothing_raised { Thread.kill(t) }
    assert_equal(nil, t.value)
  end

  def test_exit
    s = 0
    Thread.new do
      s += 1
      Thread.exit
      s += 2
    end.join
    assert_equal(1, s)
  end

  def test_wakeup
    s = 0
    t = Thread.new do
      s += 1
      Thread.stop
      s += 1
    end
    Thread.pass until t.stop?
    sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait
    assert_equal(1, s)
    t.wakeup
    Thread.pass while t.alive?
    assert_equal(2, s)
    assert_raise(ThreadError) { t.wakeup }
  ensure
    t&.kill&.join
  end

  def test_stop
    assert_in_out_err([], <<-INPUT, %w(2), [])
      begin
        Thread.stop
        p 1
      rescue ThreadError
        p 2
      end
    INPUT
  end

  def test_list
    assert_in_out_err([], <<-INPUT) do |r, e|
      t1 = Thread.new { sleep }
      Thread.pass
      t2 = Thread.new { loop { Thread.pass } }
      Thread.new { }.join
      p [Thread.current, t1, t2].map{|t| t.object_id }.sort
      p Thread.list.map{|t| t.object_id }.sort
    INPUT
      assert_equal(r.first, r.last)
      assert_equal([], e)
    end
  end

  def test_main
    assert_in_out_err([], <<-INPUT, %w(true false), [])
      p Thread.main == Thread.current
      Thread.new { p Thread.main == Thread.current }.join
    INPUT
  end

  def test_abort_on_exception
    assert_in_out_err([], <<-INPUT, %w(false 1), [])
      p Thread.abort_on_exception
      begin
        t = Thread.new {
          Thread.current.report_on_exception = false
          raise
        }
        Thread.pass until t.stop?
        p 1
      rescue
        p 2
      end
    INPUT

    assert_in_out_err([], <<-INPUT, %w(true 2), [])
      Thread.abort_on_exception = true
      p Thread.abort_on_exception
      begin
        Thread.new {
          Thread.current.report_on_exception = false
          raise
        }
        sleep 0.5
        p 1
      rescue
        p 2
      end
    INPUT

    assert_in_out_err(%w(--disable-gems -d), <<-INPUT, %w(false 2), %r".+")
      p Thread.abort_on_exception
      begin
        t = Thread.new { raise }
        Thread.pass until t.stop?
        p 1
      rescue
        p 2
      end
    INPUT

    assert_in_out_err([], <<-INPUT, %w(false true 2), [])
      p Thread.abort_on_exception
      begin
        ok = false
        t = Thread.new {
          Thread.current.report_on_exception = false
          Thread.pass until ok
          raise
        }
        t.abort_on_exception = true
        p t.abort_on_exception
        ok = 1
        sleep 1
        p 1
      rescue
        p 2
      end
    INPUT
  end

  def test_report_on_exception
    assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
    begin;
      q1 = Thread::Queue.new
      q2 = Thread::Queue.new

      assert_equal(true, Thread.report_on_exception,
                   "global flag is true by default")
      assert_equal(true, Thread.current.report_on_exception,
                   "the main thread has report_on_exception=true")

      Thread.report_on_exception = true
      Thread.current.report_on_exception = false
      assert_equal(true,
                   Thread.start {Thread.current.report_on_exception}.value,
                  "should not inherit from the parent thread but from the global flag")

      assert_warn("", "exception should be ignored silently when false") {
        th = Thread.start {
          Thread.current.report_on_exception = false
          q1.push(Thread.current.report_on_exception)
          raise "report 1"
        }
        assert_equal(false, q1.pop)
        Thread.pass while th.alive?
        assert_raise(RuntimeError) { th.join }
      }

      assert_warn(/report 2/, "exception should be reported when true") {
        th = Thread.start {
          q1.push(Thread.current.report_on_exception = true)
          raise "report 2"
        }
        assert_equal(true, q1.pop)
        Thread.pass while th.alive?
        assert_raise(RuntimeError) { th.join }
      }

      assert_warn("", "the global flag should not affect already started threads") {
        Thread.report_on_exception = false
        th = Thread.start {
          q2.pop
          q1.push(Thread.current.report_on_exception)
          raise "report 3"
        }
        q2.push(Thread.report_on_exception = true)
        assert_equal(false, q1.pop)
        Thread.pass while th.alive?
        assert_raise(RuntimeError) { th.join }
      }

      assert_warn(/report 4/, "should defaults to the global flag at the start") {
        Thread.report_on_exception = true
        th = Thread.start {
          q1.push(Thread.current.report_on_exception)
          raise "report 4"
        }
        assert_equal(true, q1.pop)
        Thread.pass while th.alive?
        assert_raise(RuntimeError) { th.join }
      }

      assert_warn(/report 5/, "should first report and then raise with report_on_exception + abort_on_exception") {
        th = Thread.start {
          Thread.current.report_on_exception = true
          Thread.current.abort_on_exception = true
          q2.pop
          raise "report 5"
        }
        assert_raise_with_message(RuntimeError, "report 5") {
          q2.push(true)
          Thread.pass while th.alive?
        }
        assert_raise(RuntimeError) { th.join }
      }
    end;
  end

  def test_ignore_deadlock
    if /mswin|mingw/ =~ RUBY_PLATFORM
      skip "can't trap a signal from another process on Windows"
    end
    assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil)
      p Thread.ignore_deadlock
      q = Queue.new
      trap(:INT){q.push :sig}
      Thread.ignore_deadlock = true
      p q.pop
    INPUT
  end

  def test_status_and_stop_p
    a = ::Thread.new {
      Thread.current.report_on_exception = false
      raise("die now")
    }
    b = Thread.new { Thread.stop }
    c = Thread.new { Thread.exit }
    e = Thread.current
    Thread.pass while a.alive? or !b.stop? or c.alive?

    assert_equal(nil, a.status)
    assert_predicate(a, :stop?)

    assert_equal("sleep", b.status)
    assert_predicate(b, :stop?)

    assert_equal(false, c.status)
    assert_match(/^#<TestThread::Thread:.* dead>$/, c.inspect)
    assert_predicate(c, :stop?)

    es1 = e.status
    es2 = e.stop?
    assert_equal(["run", false], [es1, es2])
    assert_raise(RuntimeError) { a.join }
  ensure
    b&.kill&.join
    c&.join
  end

  def test_switch_while_busy_loop
    bug1402 = "[ruby-dev:38319] [Bug #1402]"
    flag = true
    th = Thread.current
    waiter = Thread.start {
      sleep 0.1
      flag = false
      sleep 1
      th.raise(bug1402)
    }
    assert_nothing_raised(RuntimeError, bug1402) do
      nil while flag
    end
    assert(!flag, bug1402)
  ensure
    waiter&.kill&.join
  end

  def test_thread_local
    t = Thread.new { sleep }

    assert_equal(false, t.key?(:foo))

    t["foo"] = "foo"
    t["bar"] = "bar"
    t["baz"] = "baz"

    assert_equal(true, t.key?(:foo))
    assert_equal(true, t.key?("foo"))
    assert_equal(false, t.key?(:qux))
    assert_equal(false, t.key?("qux"))

    assert_equal([:foo, :bar, :baz].sort, t.keys.sort)

  ensure
    t&.kill&.join
  end

  def test_thread_local_fetch
    t = Thread.new { sleep }

    assert_equal(false, t.key?(:foo))

    t["foo"] = "foo"
    t["bar"] = "bar"
    t["baz"] = "baz"

    x = nil
    assert_equal("foo", t.fetch(:foo, 0))
    assert_equal("foo", t.fetch(:foo) {x = true})
    assert_nil(x)
    assert_equal("foo", t.fetch("foo", 0))
    assert_equal("foo", t.fetch("foo") {x = true})
    assert_nil(x)

    x = nil
    assert_equal(0, t.fetch(:qux, 0))
    assert_equal(1, t.fetch(:qux) {x = 1})
    assert_equal(1, x)
    assert_equal(2, t.fetch("qux", 2))
    assert_equal(3, t.fetch("qux") {x = 3})
    assert_equal(3, x)

    e = assert_raise(KeyError) {t.fetch(:qux)}
    assert_equal(:qux, e.key)
    assert_equal(t, e.receiver)
  ensure
    t&.kill&.join
  end

  def test_thread_local_security
    Thread.new do
      Thread.current[:foo] = :bar
      Thread.current.freeze
      assert_raise(FrozenError) do
        Thread.current[:foo] = :baz
      end
    end.join
  end

  def test_thread_local_dynamic_symbol
    bug10667 = '[ruby-core:67185] [Bug #10667]'
    t = Thread.new {}.join
    key_str = "foo#{rand}"
    key_sym = key_str.to_sym
    t.thread_variable_set(key_str, "bar")
    assert_equal("bar", t.thread_variable_get(key_str), "#{bug10667}: string key")
    assert_equal("bar", t.thread_variable_get(key_sym), "#{bug10667}: symbol key")
  end

  def test_select_wait
    assert_nil(IO.select(nil, nil, nil, 0.001))
    t = Thread.new do
      IO.select(nil, nil, nil, nil)
    end
    Thread.pass until t.stop?
    assert_predicate(t, :alive?)
  ensure
    t&.kill&.join
  end

  def test_mutex_deadlock
    m = Thread::Mutex.new
    m.synchronize do
      assert_raise(ThreadError) do
        m.synchronize do
          assert(false)
        end
      end
    end
  end

  def test_mutex_interrupt
    m = Thread::Mutex.new
    m.lock
    t = Thread.new do
      m.lock
      :foo
    end
    Thread.pass until t.stop?
    t.kill
    assert_nil(t.value)
  end

  def test_mutex_illegal_unlock
    m = Thread::Mutex.new
    m.lock
    Thread.new do
      assert_raise(ThreadError) do
        m.unlock
      end
    end.join
  end

  def test_mutex_fifo_like_lock
    m1 = Thread::Mutex.new
    m2 = Thread::Mutex.new
    m1.lock
    m2.lock
    m1.unlock
    m2.unlock
    assert_equal(false, m1.locked?)
    assert_equal(false, m2.locked?)

    m3 = Thread::Mutex.new
    m1.lock
    m2.lock
    m3.lock
    m1.unlock
    m2.unlock
    m3.unlock
    assert_equal(false, m1.locked?)
    assert_equal(false, m2.locked?)
    assert_equal(false, m3.locked?)
  end

  def test_mutex_trylock
    m = Thread::Mutex.new
    assert_equal(true, m.try_lock)
    assert_equal(false, m.try_lock, '[ruby-core:20943]')

    Thread.new{
      assert_equal(false, m.try_lock)
    }.join

    m.unlock
  end

  def test_recursive_outer
    arr = []
    obj = Struct.new(:foo, :visited).new(arr, false)
    arr << obj
    def obj.hash
      self[:visited] = true
      super
      raise "recursive_outer should short circuit intermediate calls"
    end
    assert_nothing_raised {arr.hash}
    assert(obj[:visited], "obj.hash was not called")
  end

  def test_thread_instance_variable
    bug4389 = '[ruby-core:35192]'
    assert_in_out_err([], <<-INPUT, %w(), [], bug4389)
      class << Thread.current
        @data = :data
      end
    INPUT
  end

  def test_no_valid_cfp
    skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE)
    bug5083 = '[ruby-dev:44208]'
    assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083)
    assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083)
  end

  def make_handle_interrupt_test_thread1 flag
    r = []
    ready_q = Queue.new
    done_q = Queue.new
    th = Thread.new{
      begin
        Thread.handle_interrupt(RuntimeError => flag){
          begin
            ready_q << true
            done_q.pop
          rescue
            r << :c1
          end
        }
      rescue
        r << :c2
      end
    }
    ready_q.pop
    th.raise
    begin
      done_q << true
      th.join
    rescue
      r << :c3
    end
    r
  end

  def test_handle_interrupt
    [[:never, :c2],
     [:immediate, :c1],
     [:on_blocking, :c1]].each{|(flag, c)|
      assert_equal([flag, c], [flag] + make_handle_interrupt_test_thread1(flag))
    }
    # TODO: complex cases are needed.
  end

  def test_handle_interrupt_invalid_argument
    assert_raise(ArgumentError) {
      Thread.handle_interrupt(RuntimeError => :immediate) # no block
    }
    assert_raise(ArgumentError) {
      Thread.handle_interrupt(RuntimeError => :xyzzy) {}
    }
    assert_raise(TypeError) {
      Thread.handle_interrupt([]) {} # array
    }
  end

  def for_test_handle_interrupt_with_return
    Thread.handle_interrupt(Object => :never){
      Thread.current.raise RuntimeError.new("have to be rescured")
      return
    }
  rescue
  end

  def test_handle_interrupt_with_return
    assert_nothing_raised do
      for_test_handle_interrupt_with_return
      _dummy_for_check_ints=nil
    end
  end

  def test_handle_interrupt_with_break
    assert_nothing_raised do
      begin
        Thread.handle_interrupt(Object => :never){
          Thread.current.raise RuntimeError.new("have to be rescured")
          break
        }
      rescue
      end
      _dummy_for_check_ints=nil
    end
  end

  def test_handle_interrupt_blocking
    r = nil
    q = Queue.new
    e = Class.new(Exception)
    th_s = Thread.current
    th = Thread.start {
      assert_raise(RuntimeError) {
        Thread.handle_interrupt(Object => :on_blocking){
          begin
            q.pop
            Thread.current.raise RuntimeError, "will raise in sleep"
            r = :ok
            sleep
          ensure
            th_s.raise e, "raise from ensure", $@
          end
        }
      }
    }
    assert_raise(e) {q << true; th.join}
    assert_equal(:ok, r)
  end

  def test_handle_interrupt_and_io
    assert_in_out_err([], <<-INPUT, %w(ok), [])
      th_waiting = true
      q = Queue.new

      t = Thread.new {
        Thread.current.report_on_exception = false
        Thread.handle_interrupt(RuntimeError => :on_blocking) {
          q << true
          nil while th_waiting
          # async interrupt should be raised _before_ writing puts arguments
          puts "ng"
        }
      }

      q.pop
      t.raise RuntimeError
      th_waiting = false
      t.join rescue nil
      puts "ok"
    INPUT
  end

  def test_handle_interrupt_and_p
    assert_in_out_err([], <<-INPUT, %w(:ok :ok), [])
      th_waiting = false

      t = Thread.new {
        Thread.current.report_on_exception = false
        Thread.handle_interrupt(RuntimeError => :on_blocking) {
          th_waiting = true
          nil while th_waiting
          # p shouldn't provide interruptible point
          p :ok
          p :ok
        }
      }

      Thread.pass until th_waiting
      t.raise RuntimeError
      th_waiting = false
      t.join rescue nil
    INPUT
  end

  def test_handle_interrupted?
    q = Thread::Queue.new
    Thread.handle_interrupt(RuntimeError => :never){
      done = false
      th = Thread.new{
        q.push :e
        begin
          begin
            Thread.pass until done
          rescue
            q.push :ng1
          end
          begin
            Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt?
          rescue RuntimeError
            q.push :ok
          end
        rescue
          q.push :ng2
        ensure
          q.push :ng3
        end
      }
      q.pop
      th.raise
      done = true
      th.join
      assert_equal(:ok, q.pop)
    }
  end

  def test_thread_timer_and_ensure
    assert_normal_exit(<<_eom, 'r36492', timeout: 10)
    flag = false
    t = Thread.new do
      begin
        sleep
      ensure
        1 until flag
      end
    end

    Thread.pass until t.status == "sleep"

    t.kill
    t.alive? == true
    flag = true
    t.join
_eom
  end

  def test_uninitialized
    c = Class.new(Thread) {def initialize; end}
    assert_raise(ThreadError) { c.new.start }

    bug11959 = '[ruby-core:72732] [Bug #11959]'

    c = Class.new(Thread) {def initialize; exit; end}
    assert_raise(ThreadError, bug11959) { c.new }

    c = Class.new(Thread) {def initialize; raise; end}
    assert_raise(ThreadError, bug11959) { c.new }

    c = Class.new(Thread) {
      def initialize
        pending = pending_interrupt?
        super {pending}
      end
    }
    assert_equal(false, c.new.value, bug11959)
  end

  def test_backtrace
    Thread.new{
      assert_equal(Array, Thread.main.backtrace.class)
    }.join

    t = Thread.new{}
    t.join
    assert_equal(nil, t.backtrace)
  end

  def test_thread_timer_and_interrupt
    bug5757 = '[ruby-dev:44985]'
    pid = nil
    cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read'
    opt = {}
    opt[:new_pgroup] = true if /mswin|mingw/ =~ RUBY_PLATFORM
    s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, **opt) do |in_p, out_p, err_p, cpid|
      assert IO.select([out_p], nil, nil, 10), 'subprocess not ready'
      out_p.gets
      pid = cpid
      t0 = Time.now.to_f
      Process.kill(:SIGINT, pid)
      begin
        Timeout.timeout(10) { Process.wait(pid) }
      rescue Timeout::Error
        EnvUtil.terminate(pid)
        raise
      end
      t1 = Time.now.to_f
      [$?, t1 - t0, err_p.read]
    end
    assert_equal(pid, s.pid, bug5757)
    assert_equal([false, true, false, Signal.list["INT"]],
                 [s.exited?, s.signaled?, s.stopped?, s.termsig],
                 "[s.exited?, s.signaled?, s.stopped?, s.termsig]")
    assert_include(0..2, t, bug5757)
  end

  def test_thread_join_in_trap
    assert_separately [], <<-'EOS'
    Signal.trap(:INT, "DEFAULT")
    t0 = Thread.current
    assert_nothing_raised{
      t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$)}

      Signal.trap :INT do
        t.join
      end

      t.join
    }
    EOS
  end

  def test_thread_value_in_trap
    assert_separately [], <<-'EOS'
    Signal.trap(:INT, "DEFAULT")
    t0 = Thread.current
    t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$); :normal_end}

    Signal.trap :INT do
      t.value
    end
    assert_equal(:normal_end, t.value)
    EOS
  end

  def test_thread_join_current
    assert_raise(ThreadError) do
      Thread.current.join
    end
  end

  def test_thread_join_main_thread
    Thread.new(Thread.current) {|t|
      assert_raise(ThreadError) do
        t.join
      end
    }.join
  end

  def test_main_thread_status_at_exit
    assert_in_out_err([], <<-'INPUT', ["false false aborting"], [])
q = Thread::Queue.new
Thread.new(Thread.current) {|mth|
  begin
    q.push nil
    mth.run
    Thread.pass until mth.stop?
    p :mth_stopped # don't run if killed by rb_thread_terminate_all
  ensure
    puts "#{mth.alive?} #{mth.status} #{Thread.current.status}"
  end
}
q.pop
    INPUT
  end

  def test_thread_status_in_trap
    # when running trap handler, Thread#status must show "run"
    # Even though interrupted from sleeping function
    assert_in_out_err([], <<-INPUT, %w(sleep run), [])
      Signal.trap(:INT) {
        puts Thread.current.status
        exit
      }
      t = Thread.current

      Thread.new(Thread.current) {|mth|
        Thread.pass until t.stop?
        puts mth.status
        Process.kill(:INT, $$)
      }
      sleep 0.1
    INPUT
  end

  # Bug #7450
  def test_thread_status_raise_after_kill
    ary = []

    t = Thread.new {
      assert_raise(RuntimeError) do
        begin
          ary << Thread.current.status
          sleep #1
        ensure
          begin
            ary << Thread.current.status
            sleep #2
          ensure
            ary << Thread.current.status
          end
        end
      end
    }

    Thread.pass until ary.size >= 1
    Thread.pass until t.stop?
    t.kill  # wake up sleep #1
    Thread.pass until ary.size >= 2
    Thread.pass until t.stop?
    t.raise "wakeup" # wake up sleep #2
    Thread.pass while t.alive?
    assert_equal(ary, ["run", "aborting", "aborting"])
    t.join
  end

  def test_mutex_owned
    mutex = Thread::Mutex.new

    assert_equal(mutex.owned?, false)
    mutex.synchronize {
      # Now, I have the mutex
      assert_equal(mutex.owned?, true)
    }
    assert_equal(mutex.owned?, false)
  end

  def test_mutex_owned2
    begin
      mutex = Thread::Mutex.new
      th = Thread.new {
        # lock forever
        mutex.lock
        sleep
      }

      # acquired by another thread.
      Thread.pass until mutex.locked?
      assert_equal(mutex.owned?, false)
    ensure
      th&.kill&.join
    end
  end

  def test_mutex_unlock_on_trap
    assert_in_out_err([], <<-INPUT, %w(locked unlocked false), [])
      m = Thread::Mutex.new

      trapped = false
      Signal.trap("INT") { |signo|
        m.unlock
        trapped = true
        puts "unlocked"
      }

      m.lock
      puts "locked"
      Process.kill("INT", $$)
      Thread.pass until trapped
      puts m.locked?
    INPUT
  end

  def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true
    env = {}
    env['RUBY_THREAD_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size
    env['RUBY_THREAD_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size
    out, = EnvUtil.invoke_ruby([env, '-e', script], '', true, true)
    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 * 10, 1024 * 1024 * 10, false))

    assert_operator(h_default[:thread_vm_stack_size], :>, h_0[:thread_vm_stack_size],
                    "0 thread_vm_stack_size")
    assert_operator(h_default[:thread_vm_stack_size], :<, h_large[:thread_vm_stack_size],
                    "large thread_vm_stack_size")
    assert_operator(h_default[:thread_machine_stack_size], :>=, h_0[:thread_machine_stack_size],
                    "0 thread_machine_stack_size")
    assert_operator(h_default[:thread_machine_stack_size], :<=, h_large[:thread_machine_stack_size],
                    "large thread_machine_stack_size")
    assert_equal("ok", invoke_rec('print :ok', 1024 * 1024 * 100, nil, false))
  end

  def test_vm_machine_stack_size
    script = 'def rec; print "."; STDOUT.flush; rec; end; rec'
    size_default = invoke_rec script, nil, nil
    assert_operator(size_default, :>, 0, "default size")
    size_0 = invoke_rec script, 0, nil
    assert_operator(size_default, :>, size_0, "0 size")
    size_large = invoke_rec script, 1024 * 1024 * 10, nil
    assert_operator(size_default, :<, size_large, "large size")
  end

  def test_machine_stack_size
    # check machine stack size
    # Note that machine stack size may not change size (depend on OSs)
    script = 'def rec; print "."; STDOUT.flush; 1.times{1.times{1.times{rec}}}; end; Thread.new{rec}.join'
    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, "0 size")
    size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10
    assert_operator(size_default, :<=, size_large, "large size")
  end unless /mswin|mingw/ =~ RUBY_PLATFORM

  def test_blocking_mutex_unlocked_on_fork
    bug8433 = '[ruby-core:55102] [Bug #8433]'

    mutex = Thread::Mutex.new
    mutex.lock

    th = Thread.new do
      mutex.synchronize do
        sleep
      end
    end

    Thread.pass until th.stop?
    mutex.unlock

    pid = Process.fork do
      exit(mutex.locked?)
    end

    th.kill

    pid, status = Process.waitpid2(pid)
    assert_equal(false, status.success?, bug8433)
  end if Process.respond_to?(:fork)

  def test_fork_in_thread
    bug9751 = '[ruby-core:62070] [Bug #9751]'
    f = nil
    th = Thread.start do
      unless f = IO.popen("-")
        STDERR.reopen(STDOUT)
        exit
      end
      Process.wait2(f.pid)
    end
    unless th.join(EnvUtil.apply_timeout_scale(30))
      Process.kill(:QUIT, f.pid)
      Process.kill(:KILL, f.pid) unless th.join(EnvUtil.apply_timeout_scale(1))
    end
    _, status = th.value
    output = f.read
    f.close
    assert_not_predicate(status, :signaled?, FailDesc[status, bug9751, output])
    assert_predicate(status, :success?, bug9751)
  end if Process.respond_to?(:fork)

  def test_fork_while_locked
    m = Mutex.new
    thrs = []
    3.times do |i|
      thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } }
    end
    thrs.each do |t|
      assert_predicate t.value, :success?, '[ruby-core:85940] [Bug #14578]'
    end
  end if Process.respond_to?(:fork)

  def test_fork_while_parent_locked
    skip 'needs fork' unless Process.respond_to?(:fork)
    m = Thread::Mutex.new
    nr = 1
    thrs = []
    m.synchronize do
      thrs = nr.times.map { Thread.new { m.synchronize {} } }
      thrs.each { Thread.pass }
      pid = fork do
        m.locked? or exit!(2)
        thrs = nr.times.map { Thread.new { m.synchronize {} } }
        m.unlock
        thrs.each { |t| t.join(1) == t or exit!(1) }
        exit!(0)
      end
      _, st = Process.waitpid2(pid)
      assert_predicate st, :success?, '[ruby-core:90312] [Bug #15383]'
    end
    thrs.each { |t| assert_same t, t.join(1) }
  end

  def test_fork_while_mutex_locked_by_forker
    skip 'needs fork' unless Process.respond_to?(:fork)
    m = Mutex.new
    m.synchronize do
      pid = fork do
        exit!(2) unless m.locked?
        m.unlock rescue exit!(3)
        m.synchronize {} rescue exit!(4)
        exit!(0)
      end
      _, st = Timeout.timeout(30) { Process.waitpid2(pid) }
      assert_predicate st, :success?, '[ruby-core:90595] [Bug #15430]'
    end
  end

  def test_subclass_no_initialize
    t = Module.new do
      break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")
    end
    t.class_eval do
      def initialize
      end
    end
    assert_raise_with_message(ThreadError, /C\u{30b9 30ec 30c3 30c9}/) do
      t.new {}
    end
  end

  def test_thread_name
    t = Thread.start {sleep}
    sleep 0.001 until t.stop?
    assert_nil t.name
    s = t.inspect
    t.name = 'foo'
    assert_equal 'foo', t.name
    t.name = nil
    assert_nil t.name
    assert_equal s, t.inspect
  ensure
    t.kill
    t.join
  end

  def test_thread_invalid_name
    bug11756 = '[ruby-core:71774] [Bug #11756]'
    t = Thread.start {}
    assert_raise(ArgumentError, bug11756) {t.name = "foo\0bar"}
    assert_raise(ArgumentError, bug11756) {t.name = "foo".encode(Encoding::UTF_32BE)}
  ensure
    t.kill
    t.join
  end

  def test_thread_invalid_object
    bug11756 = '[ruby-core:71774] [Bug #11756]'
    t = Thread.start {}
    assert_raise(TypeError, bug11756) {t.name = []}
  ensure
    t.kill
    t.join
  end

  def test_thread_setname_in_initialize
    bug12290 = '[ruby-core:74963] [Bug #12290]'
    c = Class.new(Thread) {def initialize() self.name = "foo"; super; end}
    assert_equal("foo", c.new {Thread.current.name}.value, bug12290)
  end

  def test_thread_interrupt_for_killed_thread
    opts = { timeout: 5, timeout_error: nil }

    # prevent SIGABRT from slow shutdown with MJIT
    opts[:reprieve] = 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?

    assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
      Thread.report_on_exception = false
      trap(:TERM){exit}
      while true
        t = Thread.new{sleep 0}
        t.raise Interrupt
        Thread.pass # allow t to finish
      end
    _end
  end

  def test_signal_at_join
    if /mswin|mingw/ =~ RUBY_PLATFORM
      skip "can't trap a signal from another process on Windows"
      # opt = {new_pgroup: true}
    end
    assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120)
    {#
      n = 1000
      sig = :INT
      trap(sig) {}
      IO.popen([EnvUtil.rubybin, "-e", "#{<<~"{#1"}\n#{<<~'};#1'}"], "r+") do |f|
        tpid = #{$$}
        sig = :#{sig}
        {#1
          STDOUT.sync = true
          while gets
            puts
            Process.kill(sig, tpid)
          end
        };#1
        assert_nothing_raised do
          n.times do
            w = Thread.start do
              sleep 30
            end
            begin
              f.puts
              f.gets
            ensure
              w.kill
              w.join
            end
          end
        end
        n.times do
          w = Thread.start { sleep 30 }
          begin
            f.puts
            f.gets
          ensure
            w.kill
            t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
            w.join(30)
            t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
            diff = t1 - t0
            assert_operator diff, :<=, 2
          end
        end
      end
    };
  end
end
