From 2548bcf7bf94d4c416bb56d59c0cc5ada7700baa Mon Sep 17 00:00:00 2001 From: Jim Gay Date: Fri, 20 Dec 2024 17:34:05 -0500 Subject: [PATCH] Simplify testing multithreaded applications Remove the need to branch based on the platform. --- test/multithreaded_test.rb | 82 +++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/test/multithreaded_test.rb b/test/multithreaded_test.rb index ffed478..961f1d9 100644 --- a/test/multithreaded_test.rb +++ b/test/multithreaded_test.rb @@ -1,11 +1,12 @@ require 'thread' require 'test_helper' require 'radius' +require 'etc' class MultithreadTest < Minitest::Test def setup - Thread.abort_on_exception + Thread.abort_on_exception = true @context = Radius::Context.new do |c| c.define_tag('thread') do |tag| "#{tag.locals.thread_id} / #{tag.globals.object_id}" @@ -13,51 +14,48 @@ def setup end end - if RUBY_PLATFORM == 'java' - require 'java' - # call once before the thread to keep from using hidden require in a thread - Radius::Parser.new - def test_runs_multithreaded - lock = java.lang.String.new("lock") - threads = [] - 1000.times do |t| - thread = Thread.new do - parser = Radius::Parser.new(@context, :tag_prefix => 'r') - parser.context.globals.thread_id = Thread.current.object_id - expected = "#{Thread.current.object_id} / "+ - "#{parser.context.globals.object_id}" - actual = parser.parse('') - assert_equal expected, actual - end - lock.synchronized do - threads << thread + def test_runs_multithreaded + thread_count = [Etc.nprocessors, 16].min + iterations_per_thread = 500 + failures = Queue.new + results = Array.new(thread_count) { [] } + threads = [] + + thread_count.times do |i| + threads << Thread.new do + local_results = [] + iterations_per_thread.times do + begin + parser = Radius::Parser.new(@context, :tag_prefix => 'r') + parser.context.globals.thread_id = Thread.current.object_id + expected = "#{Thread.current.object_id} / #{parser.context.globals.object_id}" + result = parser.parse('') + + local_results << result + + failures << "Expected: #{expected}, Got: #{result}" unless result == expected + rescue => e + failures << "Thread #{Thread.current.object_id} failed: #{e.message}\n#{e.backtrace.join("\n")}" + end end - end - lock.synchronized do - threads.each{|t| t.join } + results[i] = local_results end end - else - def test_runs_multithreaded - threads = [] - mute = Mutex.new - 1000.times do |t| - thread = Thread.new do - parser = Radius::Parser.new(@context, :tag_prefix => 'r') - parser.context.globals.thread_id = Thread.current.object_id - expected = "#{Thread.current.object_id} / "+ - "#{parser.context.globals.object_id}" - actual = parser.parse('') - assert_equal expected, actual - end - mute.synchronize do - threads << thread - end - end - mute.synchronize do - threads.each{|t| t.join } - end + + threads.each(&:join) + + # Only try to show failures if there are any + failure_message = if failures.empty? + nil + else + "Thread failures detected:\n#{failures.size} times:\n#{failures.pop(5).join("\n")}" end + + assert(failures.empty?, failure_message) + total_results = results.flatten.uniq.size + expected_unique_results = thread_count * iterations_per_thread + assert_equal expected_unique_results, total_results, + "Expected #{expected_unique_results} unique results (#{thread_count} threads × #{iterations_per_thread} iterations)" end end