Back to Repositories

Testing Thread-Safe Hash Implementation in concurrent-ruby

This test suite validates the concurrent hash implementation in the concurrent-ruby library, focusing on initialization methods and thread-safe operations. The tests ensure proper hash creation, default value handling, and concurrent access safety.

Test Coverage Overview

The test suite provides comprehensive coverage of the Concurrent::Hash class functionality.

  • Hash initialization with various argument patterns
  • Default value handling
  • Block-based initialization
  • Concurrent operations safety
  • Edge cases for empty hash creation

Implementation Analysis

The testing approach uses RSpec’s describe/context blocks to organize test scenarios logically. The implementation leverages RSpec’s let statements for setup and employs doubles for mock objects. Thread-safety testing uses multiple concurrent operations to verify data integrity.

  • Nested describe blocks for clear test organization
  • Mock object testing with doubles
  • Concurrent access testing with multiple threads

Technical Details

  • Testing Framework: RSpec
  • Concurrent Testing Tools: ThreadSafe::Test
  • Test Setup: Let blocks for shared resources
  • Mock Objects: RSpec doubles
  • Thread Management: in_thread helper method

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices for concurrent data structures.

  • Isolated test cases with clear descriptions
  • Proper setup and teardown management
  • Comprehensive edge case coverage
  • Thread-safety verification
  • Mock object usage for external dependencies

ruby-concurrency/concurrent-ruby

spec/concurrent/hash_spec.rb

            
require 'concurrent/hash'

module Concurrent
  RSpec.describe Hash do
    let!(:hsh) { described_class.new }

    describe '.[]' do
      describe 'when initializing with no arguments' do
        it do
          expect(described_class[]).to be_empty
        end
      end

      describe 'when initializing with an even number of arguments' do
        it 'creates a hash using the odd position arguments as keys and even position arguments as values' do
          expect(described_class[:hello, 'hello', :world, 'world']).to eq(hello: 'hello', world: 'world')
        end
      end

      describe 'when initializing with an array of pairs' do
        let(:array_of_pairs) { [[:hello, 'hello'], [:world, 'world']] }

        it 'creates a hash using each pair as a (key, value) pair' do
          expect(described_class[array_of_pairs]).to eq(hello: 'hello', world: 'world')
        end
      end

      describe 'when initializing with another hash as an argument' do
        let(:other_hash) { {hello: 'hello', world: 'world'} }
        let(:fake_other_hash) { double('Fake hash', to_hash: other_hash) }

        it 'creates a new hash' do
          expect(described_class[other_hash]).to_not be other_hash
        end

        it 'creates a hash with the same contents as the other hash' do
          expect(described_class[other_hash]).to eq(hello: 'hello', world: 'world')
        end

        it 'creates a hash with the results of calling #to_hash on the other array' do
          expect(described_class[fake_other_hash]).to eq(hello: 'hello', world: 'world')
        end
      end
    end

    describe '.new' do
      describe 'when initializing with no arguments' do
        it do
          expect(described_class.new).to be_empty
        end
      end

      describe 'when initialized with a default object' do
        let(:default_object) { :ruby }

        it 'uses the default object for non-existing keys' do
          hash = described_class.new(default_object)

          expect(hash[:hello]).to be :ruby
          expect(hash[:world]).to be :ruby
        end
      end

      describe 'when initialized with a block' do
        it 'calls the block for non-existing keys' do
          block_calls = []

          hash = described_class.new do |hash_instance, key|
            block_calls << [hash_instance, key]
          end

          hash[:hello]
          hash[:world]

          expect(block_calls).to eq [[hash, :hello], [hash, :world]]
        end

        it 'returns the results of calling the block for non-existing key' do
          block_results = ['hello', 'world']

          hash = described_class.new do
            block_results.shift
          end

          expect(hash[:hello]).to eq 'hello'
          expect(hash[:world]).to eq 'world'
        end
      end
    end

    context 'concurrency' do
      it do
        (1..Concurrent::ThreadSafe::Test::THREADS).map do |i|
          in_thread do
            1000.times do |j|
              hsh[i * 1000 + j] = i
              expect(hsh[i * 1000 + j]).to eq(i)
              expect(hsh.delete(i * 1000 + j)).to eq(i)
            end
          end
        end.map(&:join)
        expect(hsh).to be_empty
      end
    end
  end
end