Back to Repositories

Testing Transactional Variable Operations in concurrent-ruby

This test suite validates the transactional variable (TVar) implementation in concurrent-ruby, focusing on atomic operations and transaction management. The tests verify the core functionality of TVar initialization, value manipulation, and transaction handling including nested transactions and rollback scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of TVar operations and transaction management:

  • TVar initialization and value manipulation
  • Atomic transaction execution and rollback
  • Nested transaction behavior
  • Transaction abort and leave operations
  • Error handling scenarios

Implementation Analysis

The testing approach employs RSpec’s behavior-driven development paradigm to validate transactional integrity. Tests utilize context blocks for logical grouping and leverage RSpec’s expectation syntax for assertions. The implementation focuses on transaction isolation and atomic operations verification.

Technical Details

Testing Framework:
  • RSpec for behavior specification
  • Concurrent::TVar class for transactional variables
  • Transaction control methods: atomically, abort_transaction, leave_transaction
  • Exception handling for transaction control

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases with clear setup and expectations
  • Comprehensive error case coverage
  • Logical test organization using RSpec contexts
  • Transaction boundary testing
  • Edge case validation for nested transactions

ruby-concurrency/concurrent-ruby

spec/concurrent/tvar_spec.rb

            
require 'concurrent/tvar'

module Concurrent

  RSpec.describe TVar do

    context '#initialize' do

      it 'accepts an initial value' do
        t = TVar.new(14)
        expect(t.value).to eq 14
      end

    end

    context '#value' do

      it 'gets the value' do
        t = TVar.new(14)
        expect(t.value).to eq 14
      end

    end

    context '#value=' do

      it 'sets the value' do
        t = TVar.new(14)
        t.value = 2
        expect(t.value).to eq 2
      end

    end

  end

  RSpec.describe '#atomically' do

    it 'raises an exception when no block given' do
      expect { Concurrent::atomically }.to raise_error(ArgumentError)
    end

    it 'raises the same exception that was raised in Concurrent::atomically' do
      expect {
        Concurrent::atomically do
          raise StandardError, 'This is an error!'
        end
      }.to raise_error(StandardError, 'This is an error!')
    end

    it 'retries on abort' do
      count = 0

      Concurrent::atomically do
        if count == 0
          count = 1
          Concurrent::abort_transaction
        else
          count = 2
        end
      end

      expect(count).to eq 2
    end

    it 'commits writes if the transaction succeeds' do
      t = TVar.new(0)

      Concurrent::atomically do
        t.value = 1
      end

      expect(t.value).to eq 1
    end

    it 'undoes writes if the transaction is aborted' do
      t = TVar.new(0)

      count = 0

      Concurrent::atomically do
        if count == 0
          t.value = 1
          count = 1
          Concurrent::abort_transaction
        end
      end

      expect(t.value).to eq 0
    end

    it 'provides atomicity' do
      t1 = TVar.new(0)
      t2 = TVar.new(0)

      count = 0

      Concurrent::atomically do
        if count == 0
          count = 1
          t1.value = 1
          Concurrent::abort_transaction
          t2.value = 2
        end
      end

      expect(t1.value).to eq 0
      expect(t2.value).to eq 0
    end

    it 'nests' do
      t = TVar.new(0)

      Concurrent::atomically do
        expect(t.value).to eq 0
        t.value = 1
        Concurrent::atomically do
          expect(t.value).to eq 1
          t.value = 2
          Concurrent::atomically do
            expect(t.value).to eq 2
            t.value = 3
          end
          expect(t.value).to eq 3
          t.value = 4
        end
        expect(t.value).to eq 4
        t.value = 5
      end

      expect(t.value).to eq 5
    end

    it 'reflects transactional writes from within the same transaction' do
      t = TVar.new(0)

      Concurrent::atomically do
        expect(t.value).to eq 0
        t.value = 14
        expect(t.value).to eq 14
        t.value = 2
        expect(t.value).to eq 2
      end
    end

  end

  RSpec.describe '#abort_transaction' do

    it 'raises an exception outside an #atomically block' do
      expect { Concurrent::abort_transaction }.to raise_error(Concurrent::Transaction::AbortError)
    end

  end

  RSpec.describe '#leave_transaction' do

    it 'raises an exception outside an #atomically block' do
      expect { Concurrent::leave_transaction }.to raise_error(Concurrent::Transaction::LeaveError)
    end

    it 'neither commits nor aborts a transaction' do
      t = TVar.new(0)

      Concurrent::atomically do
        expect(t.value).to eq 0
        t.value = 14
        Concurrent::leave_transaction
      end

      expect(t.value).to eq 0
    end

  end

end