Skip to content

Added test:parallel task#9572

Merged
hsbt merged 3 commits into
masterfrom
bin-test-unit-bundler-rubyopt
May 29, 2026
Merged

Added test:parallel task#9572
hsbt merged 3 commits into
masterfrom
bin-test-unit-bundler-rubyopt

Conversation

@hsbt
Copy link
Copy Markdown
Member

@hsbt hsbt commented May 29, 2026

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

hsbt and others added 3 commits May 29, 2026 08:44
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>
Copilot AI review requested due to automatic review settings May 29, 2026 00:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:parallel rake task invoking bin/test-unit test --parallel=process.
  • Make bin/test-unit set RUBYOPT with -I for bundler/lib and test so subprocess workers inherit the same load path.
  • Replace Dir.tmpdir + @cleanup teardown 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.

@hsbt hsbt merged commit b3a02f3 into master May 29, 2026
101 checks passed
@hsbt hsbt deleted the bin-test-unit-bundler-rubyopt branch May 29, 2026 00:38
Comment thread bin/test-unit
Comment on lines 7 to +13
$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(" ")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: We can use bin/test-unit -I... (not ruby -I... bin/test-unit) instead of RUBYOPT.

This may work:

Suggested change
$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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your mention!
I understood that passing absolute path instead of relative path.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 detected

I will investigate why the -I version is not working.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand how the -I option works now. test-unit adds the specified load path to the end (not the beginning):

https://github.com/test-unit/test-unit/blob/554515668a9ce3a9823ee991dc7e4b4f0ace1f12/lib/test/unit/process-worker.rb#L12

Because of this, the system bundler to be loaded preferentially.

Copy link
Copy Markdown
Member

@kou kou May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Let's prepend load paths instead of appending them in test-unit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will create a PR for it to test-unit!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. Let's release a new version of test-unit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will create a release note tonight.

kou added a commit to test-unit/test-unit that referenced this pull request Jun 3, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants