Skip to content

Improve execution time tracking for RSpec individual test examples.#289

Merged
ArturT merged 42 commits intomasterfrom
improve-rspec-split-by-examples-execution-time-measures
Feb 21, 2025
Merged

Improve execution time tracking for RSpec individual test examples.#289
ArturT merged 42 commits intomasterfrom
improve-rspec-split-by-examples-execution-time-measures

Conversation

@ArturT
Copy link
Copy Markdown
Member

@ArturT ArturT commented Feb 19, 2025

Story

https://trello.com/c/cq5i0WDt

Description

Related to the RSpec split by examples feature.

problem

Someone gives a custom set of paths to the Knapsack Pro using KNAPSACK_PRO_TEST_FILE_LIST_SOURCE_FILE.
There could be paths like a_spec.rb[1:1]. But the build_distributions#last returns tests which are not considered slow. a_spec.rb could be considered fast. We measure execution time for the full path a_spec.rb instead of a_spec.rb[1:1].

This can lead to misleading data because node 0 could measure a_spec.rb[1:1] as a_spec.rb taking 1 minute. But node 1 could measure a_spec.rb[1:2] as a_spec.rb taking 2 seconds.
Now we have two a_spec.rb paths with different timings. They are merged on the API side. We may end up with a build with a_spec.rb taking 2s while, in fact, it took much longer because it was split by examples between 2 nodes. We expect individual test examples to be measured.

solution

Individual test examples (a_spec.rb[1:1], a_spec.rb[1:2]) are measured from now on.

Checklist reminder

  • You added the changes to the UNRELEASED section of the CHANGELOG.md, including the needed bump (ie, patch, minor, major)
  • You follow the architecture outlined below for RSpec in Queue Mode, which is a work in progress (feel free to propose changes):
    • Pure: lib/knapsack_pro/pure/queue/rspec_pure.rb contains pure functions that are unit tested.
    • Extension: lib/knapsack_pro/extensions/rspec_extension.rb encapsulates calls to RSpec internals and is integration and e2e tested.
    • Runner: lib/knapsack_pro/runners/queue/rspec_runner.rb invokes the pure code and the extension to produce side effects, which are integration and e2e tested.

… examples even though they are not considered as slow test files
…blow up the RSpec integration tests for regular mode instead of silently hide the error
@ArturT ArturT added the bug label Feb 19, 2025
Comment thread spec/knapsack_pro/config/env_spec.rb
Copy link
Copy Markdown
Contributor

@3v0k4 3v0k4 left a comment

Choose a reason for hiding this comment

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

I see that to keep things short you used "split by examples" or split_by_examples. I'd keep it consistent to not add another way of calling it, so: "split by test examples" or split_by_test_examples. If it's a throwaway variable inside a method I'm fine with a shortcut.

Example:

# ok (because if I grepped I would find the split line)

def do
  split = Env.split_by_test_examples_enabled?
  # ...
end

# not ok

@split
it "split by example"

Could you ping me after you apply the changes to take a second look please?

Comment thread .circleci/config.yml Outdated
Comment on lines +83 to +87
def self.rspec_id_path?(path)
path_with_id_regex = /.+_spec\.rb\[.+\]$/

path&.match?(path_with_id_regex)
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

recommendation

Use the same naming:

Suggested change
def self.rspec_id_path?(path)
path_with_id_regex = /.+_spec\.rb\[.+\]$/
path&.match?(path_with_id_regex)
end
def self.rspec_id_path?(path)
id_path_regex = /.+_spec\.rb\[.+\]$/
path&.match?(id_path_regex)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't like that there are multiple callers and given the path&. it seems sometimes path is nil.

When does that happen?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion

I'd go with re.match?(str) instead of the other way around. It reads better for me.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't like that there are multiple callers and given the path&. it seems sometimes path is nil.

When does that happen?

I don't know. I think it should not happen because a path should always be a string.

The rspec_id_path? method was moved in here from the lib/knapsack_pro/test_case_mergers/rspec_merger.rb, but originally it's definition comes from the API project:
https://github.com/KnapsackPro/knapsack-pro-api/commit/2ebfe5e133957df37be1d69c0ae999d8c7b0a7be/domain/project_domain/test_file/test_file_value.rb#diff-8

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I applied changes: 868dc17

Comment thread lib/knapsack_pro/adapters/rspec_adapter.rb
end

def bind_regular_mode_time_tracker
return unless KnapsackPro::Config::Env.recording_enabled?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When is it true?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

KnapsackPro::Config::Env.recording_enabled? is true when you use regular mode only.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did a little refactoring because the following methods are a bit confusing:

KnapsackPro::Config::Env.recording_enabled? # is regular mode on
KnapsackPro::Config::Env.queue_recording_enabled? # is queue mode on

I've renamed them to:

KnapsackPro::Config::Env.regular_mode?
KnapsackPro::Config::Env.queue_mode?

Comment thread lib/knapsack_pro/adapters/rspec_adapter.rb
@node_test_file_paths += test_file_paths

time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
time_tracker.scheduled_test_file_paths = @node_test_file_paths
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Each time we set what was there before plus the new batch?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes.

Each time we override the time_tracker.scheduled_test_file_paths with @node_test_file_paths.

The @node_test_file_paths is updated on each batch pulled from the Queue API.

Comment thread spec/integration/runners/queue/rspec_runner_spec.rb Outdated
Comment thread spec/integration/runners/queue/rspec_runner_spec.rb
Comment thread lib/knapsack_pro/formatters/time_tracker.rb
Comment thread lib/knapsack_pro/formatters/time_tracker.rb Outdated
@ArturT ArturT requested a review from 3v0k4 February 20, 2025 16:54
ArturT added a commit to KnapsackPro/rails-app-with-knapsack_pro that referenced this pull request Feb 20, 2025
@3v0k4
Copy link
Copy Markdown
Contributor

3v0k4 commented Feb 21, 2025

@ArturT I think the following would be clearer in the PR description:

Problem

Someone gives a set of paths to Knapsack Pro using KNAPSACK_PRO_TEST_FILE_LIST_SOURCE_FILE including a_spec.rb[1:1].

The build_distributions#last endpoint may return a_spec.rb (not a_spec.rb[1:1]), so the Knapsack Pro client measures the following execution times:

  • a_spec.rb takes as long as a_spec.rb[1:1]
  • a_spec.rb[1:1] takes 0 seconds (default time)

It may happen that:

  • node 0 measures a_spec.rb[1:1] as a_spec.rb taking 1 minute and a_spec.rb[1:1] taking 0 seconds
  • node 1 measures a_spec.rb[1:2] as a_spec.rb taking 2 seconds and a_spec.rb[1:2] taking 0 seconds

There are two a_spec.rb paths with different timings that are merged on the API side. We may end up with a build with a_spec.rb taking 2s while, in fact, it took much longer because it was split by examples between 2 nodes. We expect individual test examples to be measured.

Also, the dashboard shows:

  • a_spec.rb: 2 seconds
  • a_spec.rb[1:1]: 0 seconds
  • a_spec.rb[1:2]: 0 seconds

Copy link
Copy Markdown
Contributor

@3v0k4 3v0k4 left a comment

Choose a reason for hiding this comment

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

Fantastic work!

Comment thread lib/knapsack_pro/adapters/rspec_adapter.rb Outdated
Comment thread lib/knapsack_pro/adapters/rspec_adapter.rb
Comment thread lib/knapsack_pro/formatters/time_tracker.rb Outdated
@ArturT
Copy link
Copy Markdown
Member Author

ArturT commented Feb 21, 2025

@ArturT I think the following would be clearer in the PR description:

No. It's inaccurate.
I'll keep the original description.

Problem

Someone gives a set of paths to Knapsack Pro using KNAPSACK_PRO_TEST_FILE_LIST_SOURCE_FILE including a_spec.rb[1:1].

The build_distributions#last endpoint may return a_spec.rb (not a_spec.rb[1:1]), so the Knapsack Pro client measures the following execution times:

  • a_spec.rb takes as long as a_spec.rb[1:1]
  • a_spec.rb[1:1] takes 0 seconds (default time)

The build_distributions#last may return a_spec.rb and/or a_spec.rb[1:1]. It does not matter. The gem determines the slow test files based on whatever data it gets from the build_distributions#last API.

If the full test file path (a_spec.rb) is not determined as slow, but the Queue API returns a_spec.rb[1:1] then we measured execution time for the full path a_spec.rb. We expected a_spec.rb[1:1] to be measured.

ArturT and others added 4 commits February 21, 2025 10:16
@ArturT ArturT merged commit 4e17620 into master Feb 21, 2025
@ArturT ArturT deleted the improve-rspec-split-by-examples-execution-time-measures branch February 21, 2025 09:29
@3v0k4
Copy link
Copy Markdown
Contributor

3v0k4 commented Feb 21, 2025

My point was that the description in this PR is confusing, that's why I tried to simplify it.

You may still want to update the description in case someone else gets here and tries to understand what's going on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants