Parallelize the test suite in CI with parallel_tests#1572
Conversation
| database: awbw_test<%= "_#{ENV['CONDUCTOR_PORT']}" if ENV['CONDUCTOR_PORT'] %> | ||
| # TEST_ENV_NUMBER is set by parallel_tests to give each parallel process its | ||
| # own database (blank for the first process, "2", "3", ... for the rest). | ||
| database: awbw_test<%= "_#{ENV['CONDUCTOR_PORT']}" if ENV['CONDUCTOR_PORT'] %><%= ENV['TEST_ENV_NUMBER'] %> |
There was a problem hiding this comment.
🤖 From Claude: This TEST_ENV_NUMBER suffix is what gives each parallel process its own DB — blank for process 1 (awbw_test), then awbw_test2, etc. It composes with the existing CONDUCTOR_PORT suffix and is empty for normal single-process/local runs, so nothing changes there.
There was a problem hiding this comment.
Just a nit, not sure if "CONDUCTOR_PORT" is hard coded with conductor or something you've added locally but a more generic name would be better if other contributors need to set this port for different "assistants". Or even if you change from using conductor to using claude code for example, a generic name would be more appropriate.
There was a problem hiding this comment.
great point. ok, will do that in separate pr
| require "active_storage_validations/matchers" | ||
|
|
||
| if ENV["CI"] | ||
| if ENV["COVERAGE"] |
There was a problem hiding this comment.
🤖 From Claude: SimpleCov is now gated on COVERAGE (set only by the badge workflow on main) instead of CI. This keeps branch-coverage overhead off PR runs and sidesteps the per-process minimum_coverage check + .resultset.json merge that parallel execution would otherwise break. Trade-off: PRs lose the 20% coverage gate.
| # 4 parallel_tests processes (each with its own database) to cut wall time. | ||
| env: | ||
| PARALLEL_TEST_PROCESSORS: 4 | ||
|
|
There was a problem hiding this comment.
🤖 From Claude: PARALLEL_TEST_PROCESSORS=4 matches the 4 vCPUs on ubuntu-latest. Running 4 headless Chrome system specs concurrently is the main flakiness risk to watch — if CI gets flaky, dropping to 3 or sharding system specs into their own job is the lever.
There was a problem hiding this comment.
Pull request overview
Parallelizes the RSpec suite in GitHub Actions using parallel_tests to reduce CI wall-clock time, while adjusting coverage and test-result reporting to work cleanly with multi-process execution.
Changes:
- Add
parallel_testsand update CI to runparallel:create/parallel:load_schema+parallel_rspecacross 4 processes. - Isolate each parallel process with its own test database via
TEST_ENV_NUMBERsuffixing inconfig/database.yml. - Gate SimpleCov behind
COVERAGE=trueand write per-process RSpec JSON results undertmp/rspec_results/, updating docs and artifacts accordingly.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
spec/spec_helper.rb |
Moves SimpleCov behind COVERAGE and writes per-process JSON result files in CI. |
Gemfile |
Adds parallel_tests gem for parallelized specs. |
Gemfile.lock |
Locks parallel_tests dependency. |
config/database.yml |
Appends TEST_ENV_NUMBER to test DB name for per-process isolation. |
AGENTS.md |
Documents new coverage flag behavior and parallel run commands. |
.github/workflows/sanity-check-main.yml |
Sets COVERAGE=true for the coverage badge workflow run. |
.github/workflows/ci.yml |
Updates CI to create/load parallel DBs and run parallel_rspec, uploading a results directory artifact. |
|
@jmilljr24 @lenikadali hello! our test suite on gh is now at about 8min. do you think these changes acceptable? |
My prior knowledge told me that rspec didn't align with parallel testing but I see the gem added in this PR supports it so I say let give it a go. I will note that we are getting heavy with system tests which is contributing to the longer times. Something to consider when adding new tests. |
| database: awbw_test<%= "_#{ENV['CONDUCTOR_PORT']}" if ENV['CONDUCTOR_PORT'] %> | ||
| # TEST_ENV_NUMBER is set by parallel_tests to give each parallel process its | ||
| # own database (blank for the first process, "2", "3", ... for the rest). | ||
| database: awbw_test<%= "_#{ENV['CONDUCTOR_PORT']}" if ENV['CONDUCTOR_PORT'] %><%= ENV['TEST_ENV_NUMBER'] %> |
There was a problem hiding this comment.
Just a nit, not sure if "CONDUCTOR_PORT" is hard coded with conductor or something you've added locally but a more generic name would be better if other contributors need to set this port for different "assistants". Or even if you change from using conductor to using claude code for example, a generic name would be more appropriate.
| # TEST_ENV_NUMBER is set by parallel_tests to give each parallel process its | ||
| # own database (blank for the first process, "2", "3", ... for the rest). | ||
| database: awbw_test<%= "_#{ENV["CONDUCTOR_PORT"]}" if ENV["CONDUCTOR_PORT"] %><%= ENV["TEST_ENV_NUMBER"] %> |
The CI build-and-test job ran the full RSpec suite in a single process, making it the wall-clock bottleneck (~8 min). GitHub's ubuntu-latest runners have 4 vCPUs that sat mostly idle. Split the suite across 4 parallel_tests processes, each with its own database, to use them. - Add parallel_tests; append TEST_ENV_NUMBER to the test DB name so each process gets an isolated database. - CI now runs `parallel:create parallel:load_schema` then `parallel_rspec`. - Each process writes its own JSON results file (a single --out would clobber). - Move SimpleCov behind a COVERAGE flag set only by the coverage-badge workflow on main. It no longer runs on PRs, removing branch-coverage overhead and avoiding cross-process result-merge complexity under parallel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a61c3cf to
44f88e2
Compare
What is the goal of this PR and why is this important?
build-and-testjob runs the full RSpec suite (~280 specs, 27 of them Selenium system specs) in a single process, making it the wall-clock bottleneck (~8 min).ubuntu-latestrunners have 4 vCPUs that sat mostly idle.How did you approach the change?
parallel_testsgem.TEST_ENV_NUMBERto the test database name inconfig/database.ymlso each process gets an isolated DB (awbw_test,awbw_test2, …).build-and-testnow runsrake parallel:create parallel:load_schema, thenparallel_rspec spec/withPARALLEL_TEST_PROCESSORS=4.tmp/rspec_results/(a single--outfile would be clobbered across processes); the results artifact now uploads that directory.COVERAGEflag set only by the coverage-badge workflow onmain. It no longer runs on every PR — this removes branch-coverage overhead from PR runs and avoids the cross-process result-merge / per-processminimum_coverageproblems that parallel execution otherwise creates.Anything else to add?
minimum_coverage 20gate (coverage still tracked onmainvia the badge job). Happy to keep it on PRs instead by wiring up proper parallel SimpleCov merging if preferred.rspec --profilewould target the worst offenders.🤖 Generated with Claude Code