Added test:parallel task#9572
Conversation
test-unit's --parallel=process workers are bare ruby subprocesses that only inherit RUBYOPT, not the parent's $LOAD_PATH.unshift of bundler/lib done in spec/support/rubygems_ext.rb. Without help they load the host Ruby's bundler instead of the local copy, which now diverges on Bundler::LockfileParser#initialize and breaks Gem::RequestSet#load_lockfile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two re_sign tests wrote to a shared Dir.tmpdir/expired_cert.pem and removed it in teardown, racing each other under test-unit --parallel=process. @tempdir is already per-test and auto-cleaned, so the manual @cleanup machinery goes away with the change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps `bin/test-unit test --parallel=process` so contributors can exercise the test-unit 3.7.4+ process runner without remembering the flag set. Single-file or custom worker counts still go through bin/test-unit directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new rake test:parallel task that runs RubyGems tests in parallel using test-unit's process-based runner, reducing test time roughly by half. Supporting changes propagate load paths via RUBYOPT so subprocess workers find the local bundler, and rework two cert command tests to use the per-test @tempdir (removing a custom @cleanup teardown) so they remain isolated when run concurrently.
Changes:
- Add
test:parallelrake task invokingbin/test-unit test --parallel=process. - Make
bin/test-unitsetRUBYOPTwith-Iforbundler/libandtestso subprocess workers inherit the same load path. - Replace
Dir.tmpdir+@cleanupteardown in cert command re-sign tests with@tempdir, so parallel processes don't collide on shared paths.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| Rakefile | Adds test:parallel task delegating to bin/test-unit with --parallel=process. |
| bin/test-unit | Propagates bundler/lib and test load paths via RUBYOPT to child worker processes. |
| test/rubygems/test_gem_commands_cert_command.rb | Switches re-sign tests to per-test @tempdir and drops the custom @cleanup/teardown. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $LOAD_PATH.unshift File.expand_path("../test", __dir__) | ||
|
|
||
| # Propagate bundler/lib and test/ to subprocesses (e.g. test-unit | ||
| # --parallel=process workers) via RUBYOPT so they pick up the local bundler | ||
| # instead of the one shipped with the host Ruby. | ||
| extra_opts = %w[bundler/lib test].map {|rel| "-I#{File.expand_path("../#{rel}", __dir__)}" } | ||
| ENV["RUBYOPT"] = [*extra_opts, ENV["RUBYOPT"]].compact.join(" ") |
There was a problem hiding this comment.
FYI: We can use bin/test-unit -I... (not ruby -I... bin/test-unit) instead of RUBYOPT.
This may work:
| $LOAD_PATH.unshift File.expand_path("../test", __dir__) | |
| # Propagate bundler/lib and test/ to subprocesses (e.g. test-unit | |
| # --parallel=process workers) via RUBYOPT so they pick up the local bundler | |
| # instead of the one shipped with the host Ruby. | |
| extra_opts = %w[bundler/lib test].map {|rel| "-I#{File.expand_path("../#{rel}", __dir__)}" } | |
| ENV["RUBYOPT"] = [*extra_opts, ENV["RUBYOPT"]].compact.join(" ") | |
| # Propagate bundler/lib and test/ to subprocesses too for test-unit --parallel=process. | |
| # `test-unit -I...` uses `...` in the current process and subprocesses. | |
| ARGV.unshift("-I", File.expand_path("../bundler/lib", __dir__)) | |
| ARGV.unshift("-I", File.expand_path("../test", __dir__)) |
Cc: @tikkss
There was a problem hiding this comment.
Thanks for your mention!
I understood that passing absolute path instead of relative path.
There was a problem hiding this comment.
RUBYOPT version works fine:
$ bin/test-unit test --parallel=process -t TestGemRequestSet
Loaded suite test
Started
................................
Finished in 2.401234 seconds.
----------------------------------------------------------------------------------------------------------
32 tests, 138 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
----------------------------------------------------------------------------------------------------------
13.33 tests/s, 57.47 assertions/s
Coverage report generated for rubygems to /Users/zzz/src/github.com/ruby/rubygems/coverage.
Line Coverage: 25.28% (2231 / 8826)However, -I version does not work:
$ bin/test-unit test --parallel=process -t TestGemRequestSet
Loaded suite test
Started
............E
==========================================================================================================
Error: test_install_from_gemdeps_complex_dependencies(TestGemRequestSet): ArgumentError: unknown keyword: :lockfile_path
/Users/zzz/.rbenv/versions/ruby-dev/lib/ruby/4.1.0+1/bundler/lockfile_parser.rb:98:in 'Bundler::LockfileParser#initialize'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Class#new'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Gem::RequestSet#load_lockfile'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:326:in 'Gem::RequestSet#load_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:227:in 'Gem::RequestSet#install_from_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/test/rubygems/test_gem_request_set.rb:228:in 'TestGemRequestSet#test_install_from_gemdeps_complex_dependencies'
225: io.puts("gemspec")
226: end
227:
=> 228: rs.install_from_gemdeps gemdeps: "Gemfile" do |req, _installer|
229: installed << req.full_name
230: end
231:
==========================================================================================================
.E
==========================================================================================================
Error: test_install_from_gemdeps_lockfile(TestGemRequestSet): ArgumentError: unknown keyword: :lockfile_path
/Users/zzz/.rbenv/versions/ruby-dev/lib/ruby/4.1.0+1/bundler/lockfile_parser.rb:98:in 'Bundler::LockfileParser#initialize'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Class#new'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Gem::RequestSet#load_lockfile'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:326:in 'Gem::RequestSet#load_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:227:in 'Gem::RequestSet#install_from_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/test/rubygems/test_gem_request_set.rb:174:in 'TestGemRequestSet#test_install_from_gemdeps_lockfile'
171: io.puts 'gem "b"'
172: end
173:
=> 174: rs.install_from_gemdeps gemdeps: "gem.deps.rb" do |req, _installer|
175: installed << req.full_name
176: end
177:
==========================================================================================================
.......E
==========================================================================================================
Error: test_load_gemdeps_with_lockfile_git_section(TestGemRequestSet): ArgumentError: unknown keyword: :lockfile_path
/Users/zzz/.rbenv/versions/ruby-dev/lib/ruby/4.1.0+1/bundler/lockfile_parser.rb:98:in 'Bundler::LockfileParser#initialize'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Class#new'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Gem::RequestSet#load_lockfile'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:326:in 'Gem::RequestSet#load_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/test/rubygems/test_gem_request_set.rb:368:in 'TestGemRequestSet#test_load_gemdeps_with_lockfile_git_section'
365: LOCKFILE
366: end
367:
=> 368: rs.load_gemdeps "gem.deps.rb"
369:
370: git_set = rs.sets.find {|set| Gem::Resolver::GitSet === set }
371: refute_nil git_set, "GitSet should be created from GIT section"
==========================================================================================================
.....E
==========================================================================================================
Error: test_load_gemdeps_with_lockfile_gem_section(TestGemRequestSet): ArgumentError: unknown keyword: :lockfile_path
/Users/zzz/.rbenv/versions/ruby-dev/lib/ruby/4.1.0+1/bundler/lockfile_parser.rb:98:in 'Bundler::LockfileParser#initialize'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Class#new'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Gem::RequestSet#load_lockfile'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:326:in 'Gem::RequestSet#load_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/test/rubygems/test_gem_request_set.rb:338:in 'TestGemRequestSet#test_load_gemdeps_with_lockfile_gem_section'
335: LOCKFILE
336: end
337:
=> 338: rs.load_gemdeps "gem.deps.rb"
339:
340: lock_set = rs.sets.find {|set| Gem::Resolver::LockSet === set }
341: refute_nil lock_set, "LockSet should be created from GEM section"
==========================================================================================================
E
==========================================================================================================
Error: test_load_gemdeps_with_lockfile_path_section(TestGemRequestSet): ArgumentError: unknown keyword: :lockfile_path
/Users/zzz/.rbenv/versions/ruby-dev/lib/ruby/4.1.0+1/bundler/lockfile_parser.rb:98:in 'Bundler::LockfileParser#initialize'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Class#new'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:346:in 'Gem::RequestSet#load_lockfile'
/Users/zzz/src/github.com/ruby/rubygems/lib/rubygems/request_set.rb:326:in 'Gem::RequestSet#load_gemdeps'
/Users/zzz/src/github.com/ruby/rubygems/test/rubygems/test_gem_request_set.rb:399:in 'TestGemRequestSet#test_load_gemdeps_with_lockfile_path_section'
396: LOCKFILE
397: end
398:
=> 399: rs.load_gemdeps "gem.deps.rb"
400:
401: vendor_set = rs.sets.find {|set| Gem::Resolver::VendorSet === set }
402: refute_nil vendor_set, "VendorSet should be created from PATH section"
==========================================================================================================
...
Finished in 2.592877 seconds.
----------------------------------------------------------------------------------------------------------
32 tests, 126 assertions, 0 failures, 5 errors, 0 pendings, 0 omissions, 0 notifications
84.375% passed
----------------------------------------------------------------------------------------------------------
12.34 tests/s, 48.59 assertions/s
Coverage report generated for rubygems to /Users/zzz/src/github.com/ruby/rubygems/coverage.
Line Coverage: 25.28% (2231 / 8826)
Stopped processing SimpleCov as a previous error not related to SimpleCov has been detectedI will investigate why the -I version is not working.
There was a problem hiding this comment.
I understand how the -I option works now. test-unit adds the specified load path to the end (not the beginning):
Because of this, the system bundler to be loaded preferentially.
There was a problem hiding this comment.
OK. Let's prepend load paths instead of appending them in test-unit.
There was a problem hiding this comment.
OK, I will create a PR for it to test-unit!
There was a problem hiding this comment.
-I version also works fine! (We haven't released the new version of test-unit yet, so I added the local development version to the load path to verify it) :
$ RUBYLIB="path/to/test-unit/lib" bin/test-unit test --parallel=process -t TestGemRequestSet
Loaded suite test
Started
................................
Finished in 2.349125 seconds.
---------------------------------------------------------------------------------------------------------
32 tests, 138 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---------------------------------------------------------------------------------------------------------
13.62 tests/s, 58.75 assertions/s
Coverage report generated for rubygems to /Users/zzz/src/github.com/ruby/rubygems/coverage.
Line Coverage: 25.28% (2231 / 8826)There was a problem hiding this comment.
Good. Let's release a new version of test-unit.
There was a problem hiding this comment.
OK, I will create a release note tonight.
GitHub: ruby/rubygems#9572 Because we want to load the library under development before the system library. --------- Co-authored-by: Sutou Kouhei <kou@clear-code.com>
What was the end-user or developer problem that led to this PR?
From #9570 (comment). It reduces our test running time.
rake test: 112 seconds.rake test:parallel: 59 seconds.Make sure the following tasks are checked