# frozen_string_literal: true
module Test
module Unit
module CoreAssertions
class RactorAssertionsHost
include Assertions
include CoreAssertions
def _assertions
@_assertions ||= 0
end
def _assertions=(n)
@_assertions = n
end
end
# A pending assert_in_ractor result; see assert_in_ractor(value: false).
class RactorAssertionsPending
attr_reader :ractor
def initialize(ractor, context, reported) # :nodoc:
@ractor = ractor
@context = context
@reported = reported
end
def value
result, assertions, error = @ractor.value
@context._assertions += assertions
if error
if @reported and Test::Unit::AssertionFailedError === error
# Already reported in full from inside the Ractor; raise
# only a pointer so the failure is not reported twice.
raise error.class, "assertion failed in assert_in_ractor Ractor (full report on stderr)", error.backtrace
end
raise error
end
result
end
end
# Runs the given block inside a Ractor in this process, and returns
# the block's value. Unlike a bare Ractor.new, the block
# is executed with the assertion methods available, and assertion
# failures, pend/omit/skip, and errors raised inside the Ractor are
# re-raised in the caller, so they are reported by the test framework
# as usual.
#
# assert_in_ractor([1, 2]) do |ary|
# assert_equal 2, ary.size
# end
#
# With value: false, returns a RactorAssertionsPending
# instead of joining immediately, so the caller can coordinate with
# the running Ractor via its #ractor; call #value on it to join and
# collect the result. If the block raises, the error is reported to
# stderr in full right away (since #value may never be reached), and
# #value raises only a pointer to that report.
def assert_in_ractor(*args, value: true, &block)
omit "Ractor is not supported" unless defined?(Ractor)
omit "Ractor.shareable_proc is not supported" unless Ractor.respond_to?(:shareable_proc)
block = Ractor.shareable_proc(&block)
ractor = Ractor.new(block, !value, args) do |block, report, args|
host = RactorAssertionsHost.new
begin
[host.instance_exec(*args, &block), host._assertions, nil]
rescue Exception => e
if report
# With value: false, #value may never be reached (e.g. the
# caller is blocked coordinating with this Ractor), so the
# error is reported here in full, and #value raises only a
# pointer to this report.
begin
$stderr.puts "assert_in_ractor Ractor failed:"
$stderr.puts e.full_message(highlight: false, order: :top)
rescue Exception
end
end
[nil, host._assertions, e]
end
end
pending = RactorAssertionsPending.new(ractor, self, !value)
value ? pending.value : pending
end
end
end
end