Shmactor
Currently, Simplecov doesn't seem to cover code inside blocks passed to Ractor.new
This is a (likely temporary) gem that can be used to get around that. Shmactor is an actor implementation that has a similar interface to Ractor. But, because it uses Thread, Simplecov covers the lines of code that use it.
Installation
Typical stuff: add gem "shmactor" to your Gemfile or .gemspec file. Or even just
gem install shmactor if that's your jam.
Usage
To get proper coverage stats on the code you have within ractors, you could first run your test suite with Ractor to make sure it's behaviorally correct, and then run it with Shmactor to get a coverage report.
You can switch to Shmactor using an environment variable in your test suite (or wherever) with a technique like:
# spec/shmactor.rb
if ENV["SHMACTOR"] == "true"
require "shmactor"
Shmactor.activate!
BaseRactor = Shmactor
end
And in your non-test code you can do something like:
# lib/base_ractor.rb (or whatever)
BaseRactor ||= Ractor # rubocop:disable Lint/OrAssignmentToConstant
and use it like you would Ractor.new:
some_ractor = BaseRactor.new do
typical_setup_code
loop do
case receive
in :whatever, stuff
do_something
in :whatever_else
do_something_else
break
end
end
some_final_value
end
To get a proper coverage report, you should run the suite once without Shmactor so you actually test the real Ractor behavior, and again with Shmactor instead to get proper code-coverage. You can stitch these together.
RSpec example
Here's an example that uses RSpec but you can of course do this however makes sense to you for the tools your using:
We will setup a file to make use of Shmactor based on an environment variable (This needs to be loaded before you load the code you are testing.):
# spec/support/simplecov.rb
require_relative "shmactor"
require "simplecov"
command_name = Shmactor.activated? ? "shmactor" : "ractor"
SimpleCov.root "#{__dir__}/../../"
SimpleCov.command_name command_name
SimpleCov.coverage_dir "coverage/#{command_name}"
SimpleCov.start do
add_filter "spec/support/"
enable_coverage :branch
# This avoids printing out info about coverage generated by HTTPFormatter
formatter Class.new { def format(_result) = nil }
end
Then we can setup running the suite both ways and stitching together the coverage report:
# Rakefile
# ...typical Rakefile stuff...
task default: [:spec, "spec:shmactor", "spec:collate_coverage", :rubocop]
desc "Run the test suite using shmactors (for line coverage on code inside ractors)"
task "spec:shmactor" do
# It is important that we shell-out here to not interfere with the `spec` task's run
unless system "SHMACTOR=true bundle exec rspec"
exit $?.exitstatus
end
end
desc "Collate coverage from all test suite runs"
task "spec:collate_coverage" do
require "simplecov"
SimpleCov.root __dir__
resultsets = Dir["coverage/*/.resultset.json"]
puts
SimpleCov.collate resultsets do
enable_coverage :branch
minimum_coverage line: 100, branch: 100
end
puts
end
This will run the suite both ways and put the report together in the spec:collate_coverage step.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ractor-shack/shmactor
If you happen to need help with the gem please reach out!
License
This project is licensed under the MPL-2.0 license. Please see LICENSE.txt for more info.