Back to Repositories

Testing SimpleExecutorService Thread Management in concurrent-ruby

This test suite validates the SimpleExecutorService implementation in concurrent-ruby, focusing on thread creation and execution behavior. It verifies both instance and class-level methods for posting tasks with and without arguments.

Test Coverage Overview

The test suite provides comprehensive coverage of SimpleExecutorService functionality.

Key areas tested include:
  • Task execution with and without arguments
  • Thread creation verification
  • Method aliasing (#<<)
  • Instance and class-level method behavior
Edge cases covered include multiple argument handling and thread isolation from global executor.

Implementation Analysis

The testing approach uses RSpec’s shared examples pattern to verify executor service behavior. It employs CountDownLatch for synchronization testing and mock expectations to verify thread creation.

Technical implementation details:
  • Shared behavior verification using it_should_behave_like
  • Thread creation verification using expect().to receive()
  • Asynchronous execution validation using CountDownLatch

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • concurrent-ruby’s CountDownLatch for synchronization
  • Mock objects for thread creation verification
  • Shared examples for common executor behavior

Best Practices Demonstrated

The test suite exemplifies several testing best practices in concurrent programming verification.

Notable practices include:
  • Isolation of thread creation from global executor
  • Proper synchronization using CountDownLatch
  • Comprehensive argument handling verification
  • DRY implementation through shared examples
  • Clear separation of instance and class-level behavior testing

ruby-concurrency/concurrent-ruby

spec/concurrent/executor/simple_executor_service_spec.rb

            
require 'concurrent/executor/simple_executor_service'
require 'concurrent/configuration'
require_relative 'executor_service_shared'

module Concurrent

  RSpec.describe SimpleExecutorService do

    subject { SimpleExecutorService.new }

    it_should_behave_like :executor_service

    context '#post' do

      subject { SimpleExecutorService.new }

      it 'creates a new thread for a call without arguments' do
        thread = in_thread{ nil }
        expect(Thread).to receive(:new).with(no_args()).and_return(thread)
        expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args())
        subject.post{ nil }
      end

      it 'executes a call without arguments' do
        latch = CountDownLatch.new(1)
        subject.post{ latch.count_down }
        expect(latch.wait(1)).to be_truthy
      end

      it 'creates a new thread for a call with arguments' do
        thread = in_thread{ nil }
        expect(Thread).to receive(:new).with(1,2,3).and_return(thread)
        expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args())
        subject.post(1,2,3){ nil }
      end

      it 'executes a call with one argument' do
        latch = CountDownLatch.new(3)
        subject.post(3){|count| count.times{ latch.count_down } }
        expect(latch.wait(1)).to be_truthy
      end

      it 'executes a call with multiple arguments' do
        latch = CountDownLatch.new(10)
        subject.post(1,2,3,4){|*count| count.reduce(:+).times{ latch.count_down } }
        expect(latch.wait(1)).to be_truthy
      end

      it 'aliases #<<' do
        thread = in_thread{ nil }
        expect(Thread).to receive(:new).with(no_args()).and_return(thread)
        expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args())
        subject << proc{ nil }
      end
    end

    context 'SimpleExecutorService.post' do

      subject { SimpleExecutorService }

      it 'creates a new thread for a call without arguments' do
        thread = in_thread{ nil }
        expect(Thread).to receive(:new).with(no_args()).and_return(thread)
        expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args())
        subject.post{ nil }
      end

      it 'executes a call without arguments' do
        latch = CountDownLatch.new(1)
        subject.post{ latch.count_down }
        expect(latch.wait(1)).to be_truthy
      end

      it 'creates a new thread for a call with arguments' do
        thread = in_thread{ nil }
        expect(Thread).to receive(:new).with(1,2,3).and_return(thread)
        expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args())
        subject.post(1,2,3){ nil }
      end

      it 'executes a call with one argument' do
        latch = CountDownLatch.new(3)
        subject.post(3){|count| count.times{ latch.count_down } }
        expect(latch.wait(1)).to be_truthy
      end

      it 'executes a call with multiple arguments' do
        latch = CountDownLatch.new(10)
        subject.post(1,2,3,4){|*count| count.reduce(:+).times{ latch.count_down } }
        expect(latch.wait(1)).to be_truthy
      end

      it 'aliases #<<' do
        thread = in_thread{ nil }
        expect(Thread).to receive(:new).with(no_args()).and_return(thread)
        expect(Concurrent.global_fast_executor).not_to receive(:post).with(any_args())
        subject << proc{ nil }
      end
    end
  end
end