RSpec plugin for Trunk Flaky Tests. This gem automatically uploads test results to detect and quarantine flaky tests. It integrates with RSpec to provide automatic flaky test detection, quarantining, and analytics. You can find the uploaded gem in rubygems.
This gem provides an RSpec plugin that:
- Automatically uploads test results from your CI jobs
- Enables accurate flaky test detection
- Automatically quarantines flaky tests
- Provides analytics on test stability
The gem includes a native Rust extension (rspec_trunk_flaky_tests) that provides core parsing and validation functionality.
- Ruby 3.0 or later
- Bundler
- Rust and Cargo (for building the native extension)
rb-sysgem (installed automatically via dependencies)
Install dependencies:
bundle installBuild the Rust extension:
bundle exec rake compileThis will compile the Rust code and generate the native extension in lib/rspec_trunk_flaky_tests/.
Build the gem package:
bundle exec rake buildThe gem will be built and available in pkg/.
Build the native extension for a specific platform:
bundle exec rake native[x86_64-linux]Supported platforms:
x86_64-linuxaarch64-linuxarm64-darwinx86_64-darwin
Run the test suite:
bundle exec rake testThis will:
- Compile the native extension (if needed)
- Run all RSpec tests in the
test/directory
The default rake task runs the tests:
bundle exec rakeAdd the gem to your Gemfile:
gem 'rspec_trunk_flaky_tests'Then require it in your spec_helper.rb or rails_helper.rb:
require 'trunk_spec_helper'For a complete list of environment variables that the gem accepts, see lib/trunk_spec_helper.rb. The gem uses the same environment variables as the Trunk Analytics CLI for configuration overrides.
⚠️ Experimental Feature: This feature is experimental. Please reach out to support@trunk.io before attempting to use it.
When TRUNK_LOCAL_UPLOAD_DIR is set to a directory path, the RSpec gem will generate an internal.bin file and save it locally, relative to where the test was invoked. This disables the automatic upload to Trunk servers.
Use case: This is useful when uploads are failing directly within the context of RSpec, but you still want to use quarantining. By generating the internal.bin file locally, you can then upload it separately using the Trunk Analytics CLI.
Usage:
TRUNK_LOCAL_UPLOAD_DIR=./test-results bundle exec rspecThis will create an internal.bin file in the ./test-results directory (relative to where the command was run).
Uploading with the CLI:
After generating the internal.bin file, you can upload it using the Trunk Analytics CLI. The CLI supports taking internal.bin files as input when running the upload command:
trunk upload --test-reports ./test-results/internal.binThe CLI will automatically detect that the file is an internal.bin file (by its .bin extension) and process it accordingly, allowing you to still benefit from quarantining and other features.
Understanding the quarantining flow helps you know what to expect when using this gem. Here's a detailed walkthrough of what happens when you run your tests:
-
Test Command Invocation
- When you run
bundle exec rspec, the gem initializes a globalTestReportinstance that will track all test results throughout the run.
- When you run
-
Test Execution with Lazy Quarantine Fetching
- Tests run one by one as normal.
- When a test fails, the gem intercepts the failure through RSpec's
set_exceptionhook. - On the first failure, the gem makes an API call to fetch the list of quarantined tests from Trunk servers. This list is then cached in memory for the remainder of the test run to minimize API calls. The list is also cached on disk with a TTL, configurable via the
TRUNK_QUARANTINED_TESTS_DISK_CACHE_TTL_SECSenvironment variable (default 300s = 5m). - For each subsequent failure, the gem checks the cached quarantine list (no additional API calls).
-
Quarantine Check and Exception Override
- When a test fails, the gem:
- Generates a unique test identifier based on the test's location, name, classname, and file path
- Checks if this identifier exists in the cached quarantine list
- If quarantined: Stores the exception in test metadata but returns
nilinstead of setting the exception. This makes RSpec treat the test as passing, even though it actually failed. - If not quarantined: Sets the exception normally, causing RSpec to mark the test as failed.
- When a test fails, the gem:
-
Test Result Tracking
- After each test completes (whether it passed, failed, or was quarantined), the
TrunkAnalyticsListenerrecords the test result in theTestReport. - Quarantined tests are marked with
is_quarantined: truein the report, preserving the original failure information for analytics purposes.
- After each test completes (whether it passed, failed, or was quarantined), the
-
Upload After All Tests Complete
- Once all tests have finished running, RSpec calls the
closehook onTrunkAnalyticsListener. - The gem serializes all test results (including quarantined tests) into an
internal.binfile (protobuf format). - The
internal.binfile is then uploaded to Trunk servers (unlessTRUNK_LOCAL_UPLOAD_DIRis set).
- Once all tests have finished running, RSpec calls the
-
Exit Code Determination
- RSpec determines the exit code based on whether any non-quarantined tests failed.
- If all failures were quarantined, RSpec exits with code
0(success). - If any non-quarantined tests failed, RSpec exits with a non-zero code (failure).
Here's a concrete example of what happens during a test run:
1. Invoke: bundle exec rspec
2. Run test 1 → fails
→ API call: Fetch quarantined test list (first time, cached)
→ Check: Is test 1 quarantined? → Yes
→ Override exception: Test 1 marked as passing (but failure recorded)
3. Run test 2 → fails
→ Check cached quarantine list (no API call)
→ Check: Is test 2 quarantined? → Yes
→ Override exception: Test 2 marked as passing (but failure recorded)
4. Run test 3 → passes
→ No quarantine check needed
5. All tests complete
→ Upload: internal.bin with all test results (including quarantined failures)
6. Exit: Code 0 (all failures were quarantined)- Lazy Loading: The quarantine list is only fetched when the first test fails. If all tests pass, no API call is made.
- Caching: Once fetched, the quarantine list is cached for the entire test run to minimize API calls and improve performance.
- Exception Override: When a test is quarantined, the exception is stored in metadata but not set on the test, making RSpec treat it as passing. The original failure information is still preserved in the uploaded report.
- Exit Code: The exit code reflects whether any non-quarantined tests failed, allowing CI/CD pipelines to fail appropriately when real (non-quarantined) failures occur.
- Test Reporting: All test results, including quarantined failures, are uploaded to Trunk for analytics and tracking purposes.
lib/- Ruby library coderspec_trunk_flaky_tests.rb- Main library entry pointtrunk_spec_helper.rb- RSpec integrationrspec_trunk_flaky_tests/- Native extension binaries
ext/rspec_trunk_flaky_tests/- Rust source code for the native extensiontest/- RSpec test filesspec/- Test configurationrspec_trunk_flaky_tests.gemspec- Gem specification
- Clone the repository
- Install dependencies:
bundle install - Build the extension:
bundle exec rake compile - Run tests:
bundle exec rake test
The extension uses the dev profile by default when running tests. To use a different profile, set the RB_SYS_CARGO_PROFILE environment variable:
RB_SYS_CARGO_PROFILE=release bundle exec rake testTo compile the gem locally you can run using rb-sys-dock. This does require that docker is installed and running.
rb-sys-dock --platform arm64-darwin --build --directory rspec-trunk-flaky-testsThe gem is released using the GitHub Actions workflow at .github/workflows/release_ruby_gem.yml.
To release a new version:
- Trigger the workflow manually via GitHub Actions UI or API
- Provide the release tag (version number) as input
- The workflow will:
- Build the gem for all supported platforms (
x86_64-linux,aarch64-linux,arm64-darwin,x86_64-darwin) - Test the gem on all platforms with Ruby versions 3.0, 3.1, 3.2, 3.3, and 3.4
- Publish the gem to RubyGems if all tests pass
- Build the gem for all supported platforms (
The workflow automatically handles cross-compilation, testing, and publishing for all supported platforms.