Flatware

repository·master·Indexed 43 days ago

https://github.com/briandunn/flatware

A test parallelization tool designed to reduce execution time by forking workers to run Cucumber and RSpec suites in parallel. It includes specialized runner gems (flatware-rspec, flatware-cucumber), a CLI for managing worker counts and job distribution, and lifecycle hooks (before_fork, after_fork) for optimizing Rails and ActiveRecord application startup.

Tokens
7.9K
Snippets
39
Records
45
Agent score
55%

What's inside Flatware

  1. Install Flatware

    master

    To use Flatware, add the appropriate runner gems to your Gemfile. It is recommended to use require: false so they are only loaded when explicitly called via the CLI.

    For RSpec support:

    gem 'flatware-rspec', require: false

    For Cucumber support:

    gem 'flatware-cucumber', require: false

    After adding to your Gemfile, run bundle install to complete the installation.

    gem 'flatware-rspec', require: false    # for RSpec
    gem 'flatware-cucumber', require: false # for Cucumber
  2. Configure Flatware for Rails and ActiveRecord

    master

    When using Flatware with a Rails application and ActiveRecord, you must ensure each worker uses its own database.

    1. Update config/database.yml: Append the TEST_ENV_NUMBER environment variable to your test database name:
    test:
      database: foo_test<%=ENV['TEST_ENV_NUMBER']%>
    1. Prepare Databases: Use flatware fan to run the preparation task across all workers:
    $ rake db:setup
    $ flatware fan rake db:test:prepare
    1. Run Tests:
    $ flatware rspec && flatware cucumber
  3. Run Cucumber or RSpec suites with Flatware

    master

    Flatware provides CLI commands to parallelize your existing test suites.

    Cucumber: Run the entire suite with default Cucumber options using:

    $ flatware cucumber

    RSpec: Run the entire suite with default RSpec options using:

    $ flatware rspec

    Note on RSpec Load Balancing: The RSpec runner can balance worker loads based on last run times if you have example_status_persistence_file_path configured in your RSpec settings. To ensure this works, the configuration must be loaded before specs run (e.g., via --require spec_helper in your .rspec file).

    $ flatware cucumber
    $ flatware rspec
  4. Integrate SimpleCov with Flatware

    master

    To get accurate coverage statistics during parallel runs, you must initialize SimpleCov within the after_fork hook of your Flatware configuration.

    Inside your Flatware.configure block, add:

    conf.after_fork do |test_env_number|
      SimpleCov.at_fork.call(test_env_number)
      # ... other after_fork logic ...
    end
  5. Use Flatware::RSpec::Checkpoint to track RSpec test status

    master

    The Flatware::RSpec::Checkpoint class acts as a marshallable container that collects run details from RSpec workers. It listens for specific RSpec events (like failures, pending examples, or deprecations) and stores them in an events hash. This allows you to aggregate test run data across different processes or workers.

    Key features:

    • Event Listening: It automatically captures dump_failures, dump_pending, dump_profile, dump_summary, and deprecation events.
    • Aggregation: You can combine multiple Checkpoint instances using the + operator to create a final run summary.
    • Data Access: Provides methods to access specific run details like summary, deprecations, and profile.
    # Example of how Checkpoint aggregates data (conceptual)
    
    # Assuming checkpoints are collected from different workers
    combined_checkpoint = checkpoint_worker_1 + checkpoint_worker_2
    
    puts "Failures detected? #{combined_checkpoint.failures?}"
    puts "Summary: #{combined_checkpoint.summary}"
    puts "Deprecations: #{combined_checkpoint.deprecations}"
  6. How Flatware workers are spawned and configured

    master

    Flatware uses a process-forking model to manage parallel test execution. When calling Flatware::Worker.spawn, the library executes a before_fork hook from the global configuration, then forks the specified number of worker processes.

    Inside each worker process:

    1. The process name is updated to flatware worker [id].
    2. The TEST_ENV_NUMBER environment variable is set to the worker's index.
    3. An after_fork hook is executed, passing the worker index.
    4. The worker enters a listen loop, communicating with a sink via DRb (Distributed Ruby).
    # Conceptual usage of spawning workers
    Flatware::Worker.spawn(
      count: 4,
      runner: my_test_runner,
      sink: 'druby://localhost:9999'
    )
  7. Install required Flatware runner gems

    master

    The core flatware gem provides the CLI, but for specialized test runners, you should install the corresponding gems. If these are not installed, the CLI will warn you that it is missing useful commands.

    Available runner gems:

    • flatware-rspec
    • flatware-cucumber
  8. Troubleshoot PG gem segmentation faults

    master

    If you encounter a segmentation fault when starting Flatware, it may be related to the pg gem. To resolve this, add the following environment variable to the top of your Flatware helper file:

    ENV["PGGSSENCMODE"] = "disable"
  9. Use Flatware lifecycle hooks for faster startup

    master

    To avoid booting your Rails application repeatedly for every worker, use Flatware.configure in a helper file (e.g., spec/flatware_helper.rb). This allows you to boot the app once in the before_fork block and re-establish connections in the after_fork block.

    Implementation Pattern:

    Flatware.configure do |conf|
      conf.before_fork do
        require 'rails_helper'
        ActiveRecord::Base.connection.disconnect!
      end
    
      conf.after_fork do |test_env_number|
        # If using SimpleCov, call it here:
        # SimpleCov.at_fork.call(test_env_number)
    
        config = ActiveRecord::Base.connection_db_config.configuration_hash
        ActiveRecord::Base.establish_connection(
          config.merge(
            database: config.fetch(:database) + test_env_number.to_s
          )
        )
      end
    end

    Usage: Run your tests by requiring this helper:

    $ bundle exec flatware rspec -r ./spec/flatware_helper
    Flatware.configure do |conf|
      conf.before_fork do
        require 'rails_helper'
        ActiveRecord::Base.connection.disconnect!
      end
    
      conf.after_fork do |test_env_number|
        config = ActiveRecord::Base.connection_db_config.configuration_hash
        ActiveRecord::Base.establish_connection(
          config.merge(
            database: config.fetch(:database) + test_env_number.to_s
          )
        )
      end
    end
  10. Configure JobBuilder via RSpec configuration

    master

    The JobBuilder delegates several key configuration settings to the global RSpec.configuration. To control how jobs are built, you can configure the following attributes on your RSpec configuration object:

    • files_to_run: The list of spec files that need to be distributed.
    • example_status_persistence_file_path: The file path where RSpec's example statuses (including run times) are stored. JobBuilder uses this to calculate the workload balance.
    • command: JobBuilder explicitly sets the RSpec command to 'rspec' on the configuration singleton during initialization.