From 41064f5eb68710ad4be300a07f61da3f736b5677 Mon Sep 17 00:00:00 2001 From: Connor Shea <2977353+connorshea@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:36:35 -0600 Subject: [PATCH 1/2] perf: build before/after(:all) prefixes once per group in the TimeTracker `add_hooks_time` re-derived the same `group_id_path[0..-2]` string for every (example, group) pair, and `reduce` over a hash allocates an array per pair. Build the prefixes once per group and sum with `each`. For 500 test files with 40 examples and 8 groups each: 356k allocations / 20.7 ms -> 9k / 8.8 ms. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 2 ++ lib/knapsack_pro/formatters/time_tracker.rb | 22 ++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59728345..0faa432f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Unreleased +* (patch) Reduce the number of object allocations the RSpec time tracker makes when adding `before/after(:all)` hooks time to test examples. + ### 10.0.1 * Add support for [File Paths Encryption](https://docs.knapsackpro.com/ruby/encryption/) to [Retry only Failures](https://docs.knapsackpro.com/ruby/retry-only-failures/). diff --git a/lib/knapsack_pro/formatters/time_tracker.rb b/lib/knapsack_pro/formatters/time_tracker.rb index 01baabd6..000b442f 100644 --- a/lib/knapsack_pro/formatters/time_tracker.rb +++ b/lib/knapsack_pro/formatters/time_tracker.rb @@ -128,16 +128,28 @@ def top_level_group?(group) end def add_hooks_time(group, time_all_by_group_id_path) - group.each do |_, example| + return if time_all_by_group_id_path.empty? + + # `group_id_path` without its trailing `]` is compared against every + # example of the group, so build it once per group instead of per pair. + hooks_time = time_all_by_group_id_path.map do |group_id_path, time| + [group_id_path, group_id_path[0..-2], time] + end + + group.each_value do |example| next if example[:time_execution] == 0.0 - example[:time_execution] += time_all_by_group_id_path.reduce(0.0) do |sum, (group_id_path, time)| + path = example[:path] + sum = 0.0 + hooks_time.each do |group_id_path, group_id_path_prefix, time| # :path is a file path (a_spec.rb), sum any before/after(:all) in the file - next sum + time if group_id_path.start_with?(example[:path]) # :path is an id path (a_spec.rb[1:1]), sum any before/after(:all) above it - next sum + time if example[:path].start_with?(group_id_path[0..-2]) - sum + if group_id_path.start_with?(path) || path.start_with?(group_id_path_prefix) + sum += time + end end + + example[:time_execution] += sum end end From 720821e8ff19ffc999e79c96bdfa993734b86ee3 Mon Sep 17 00:00:00 2001 From: Connor Shea <2977353+connorshea@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:56:05 -0600 Subject: [PATCH 2/2] refactor: keep each hooks-time comment on its own condition Merging the two `start_with?` checks into a single `if` left the two explanatory comments stacked above it, so neither one pointed at the branch it describes. Split them back apart with `next`, which also keeps the method's shape closer to what it replaced. Co-Authored-By: Claude Opus 5 (1M context) --- lib/knapsack_pro/formatters/time_tracker.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/knapsack_pro/formatters/time_tracker.rb b/lib/knapsack_pro/formatters/time_tracker.rb index 000b442f..57d21aab 100644 --- a/lib/knapsack_pro/formatters/time_tracker.rb +++ b/lib/knapsack_pro/formatters/time_tracker.rb @@ -131,7 +131,7 @@ def add_hooks_time(group, time_all_by_group_id_path) return if time_all_by_group_id_path.empty? # `group_id_path` without its trailing `]` is compared against every - # example of the group, so build it once per group instead of per pair. + # example of the group, so build it once instead of once per example. hooks_time = time_all_by_group_id_path.map do |group_id_path, time| [group_id_path, group_id_path[0..-2], time] end @@ -143,10 +143,9 @@ def add_hooks_time(group, time_all_by_group_id_path) sum = 0.0 hooks_time.each do |group_id_path, group_id_path_prefix, time| # :path is a file path (a_spec.rb), sum any before/after(:all) in the file + next sum += time if group_id_path.start_with?(path) # :path is an id path (a_spec.rb[1:1]), sum any before/after(:all) above it - if group_id_path.start_with?(path) || path.start_with?(group_id_path_prefix) - sum += time - end + next sum += time if path.start_with?(group_id_path_prefix) end example[:time_execution] += sum