-
-
Notifications
You must be signed in to change notification settings - Fork 766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add --only-pending
CLI option
#3068
base: main
Are you sure you want to change the base?
Changes from 5 commits
65ae346
29ebda6
f653f00
0971109
998f2af
f7a8d80
c9dfdd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
Feature: Using the `--only-pending` option | ||
|
||
The `--only-pending` option filters what examples are run so that only those that failed the last time they ran are executed. To use this option, you first have to configure `config.example_status_persistence_file_path`, which RSpec will use to store the status of each example the last time it ran. | ||
|
||
Either of these options can be combined with another a directory or file name; RSpec will run just the failures from the set of loaded examples. | ||
|
||
Background: | ||
Given a file named "spec/spec_helper.rb" with: | ||
"""ruby | ||
RSpec.configure do |c| | ||
c.example_status_persistence_file_path = "examples.txt" | ||
end | ||
""" | ||
And a file named ".rspec" with: | ||
""" | ||
--require spec_helper | ||
--order random | ||
--format documentation | ||
""" | ||
And a file named "spec/array_spec.rb" with: | ||
"""ruby | ||
RSpec.describe 'Array' do | ||
it "checks for inclusion of 1" do | ||
expect([1, 2]).to include(1) | ||
end | ||
|
||
it "checks for inclusion of 2", skip: "just not ready for this yet..." do | ||
expect([1, 2]).to include(2) | ||
end | ||
|
||
it "checks for inclusion of 3" do | ||
expect([1, 2]).to include(3) # failure | ||
end | ||
end | ||
""" | ||
And a file named "spec/string_spec.rb" with: | ||
"""ruby | ||
RSpec.describe 'String' do | ||
it "checks for inclusion of 'foo'" do | ||
expect("food").to include('foo') | ||
end | ||
|
||
it "checks for inclusion of 'bar'" do | ||
expect("food").to include('bar') # failure | ||
end | ||
|
||
it "checks for inclusion of 'baz'" do | ||
expect("bazzy").to include('baz') | ||
end | ||
|
||
it "checks for inclusion of 'foobar'" do | ||
expect("food").to include('foobar') # failure | ||
end | ||
|
||
it "checks for inclusion of 'sum'", skip: "just not ready for this yet..." do | ||
expect("lorem ipsum").to include('sum') | ||
end | ||
|
||
it "checks for inclusion of 'sit'", skip: "...nor am I ready for this..." do | ||
expect("dolor sit").to include('sit') | ||
end | ||
end | ||
""" | ||
And a file named "spec/passing_spec.rb" with: | ||
"""ruby | ||
puts "Loading passing_spec.rb" | ||
|
||
RSpec.describe "A passing spec" do | ||
it "passes" do | ||
expect(1).to eq(1) | ||
end | ||
end | ||
""" | ||
And I have run `rspec` once, resulting in "10 examples, 3 failures, 3 pending" | ||
|
||
Scenario: Running `rspec --only-pending` loads only spec files with failures and runs only the failures | ||
When I run `rspec --only-pending` | ||
Then the output from "rspec --only-pending" should contain "3 examples, 0 failures, 3 pending" | ||
And the output from "rspec --only-pending" should not contain "Loading passing_spec.rb" | ||
|
||
Scenario: Combine `--only-pending` with a file name | ||
When I run `rspec spec/array_spec.rb --only-pending` | ||
Then the output should contain "1 example, 0 failures, 1 pending" | ||
When I run `rspec spec/string_spec.rb --only-pending` | ||
Then the output should contain "2 examples, 0 failures, 2 pending" | ||
|
||
Scenario: Running `rspec --only-pending` with spec files that pass doesn't run anything | ||
When I run `rspec spec/passing_spec.rb --only-pending` | ||
Then it should pass with "0 examples, 0 failures" | ||
|
||
Scenario: Clear error given when using `--only-pending` without configuring `example_status_persistence_file_path` | ||
Given I have not configured `example_status_persistence_file_path` | ||
When I run `rspec --only-pending` | ||
Then it should fail with "To use `--only-failures` or `--only-pending`, you must first set `config.example_status_persistence_file_path`." |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -179,7 +179,7 @@ def deprecation_stream=(value) | |
|
||
# @macro define_reader | ||
# The file path to use for persisting example statuses. Necessary for the | ||
# `--only-failures` and `--next-failure` CLI options. | ||
# `--only-failures`, `--only-pending`, and `--next-failure` CLI options. | ||
# | ||
# @overload example_status_persistence_file_path | ||
# @return [String] the file path | ||
|
@@ -188,7 +188,7 @@ def deprecation_stream=(value) | |
define_reader :example_status_persistence_file_path | ||
|
||
# Sets the file path to use for persisting example statuses. Necessary for the | ||
# `--only-failures` and `--next-failure` CLI options. | ||
# `--only-failures`, `--only-pending`, and `--next-failure` CLI options. | ||
def example_status_persistence_file_path=(value) | ||
@example_status_persistence_file_path = value | ||
clear_values_derived_from_example_status_persistence_file_path | ||
|
@@ -199,9 +199,24 @@ def example_status_persistence_file_path=(value) | |
define_reader :only_failures | ||
alias_method :only_failures?, :only_failures | ||
|
||
# @macro define_reader | ||
# Indicates if the `--only-pending` flag is being used. | ||
define_reader :only_pending | ||
alias_method :only_pending?, :only_pending | ||
|
||
# @private | ||
def only_flag_set? | ||
only_failures? || only_pending? | ||
end | ||
|
||
# @private | ||
def only_failures_but_not_configured? | ||
only_failures? && !example_status_persistence_file_path | ||
def multiple_only_flags? | ||
only_failures && only_pending? | ||
end | ||
|
||
# @private | ||
def only_flag_but_not_configured? | ||
only_flag_set? && !example_status_persistence_file_path | ||
end | ||
|
||
# @macro define_reader | ||
|
@@ -1143,6 +1158,14 @@ def spec_files_with_failures | |
end.to_a | ||
end | ||
|
||
# @private | ||
def spec_files_with_pending | ||
@spec_files_with_pending ||= last_run_statuses.inject(Set.new) do |files, (id, status)| | ||
files << Example.parse_id(id).first if status == PENDING_STATUS | ||
files | ||
end.to_a | ||
end | ||
|
||
# Creates a method that delegates to `example` including the submitted | ||
# `args`. Used internally to add variants of `example` like `pending`: | ||
# @param name [String] example name alias | ||
|
@@ -2196,15 +2219,27 @@ def run_suite_hooks(hook_description, hooks) | |
end | ||
end | ||
|
||
def get_files_to_run(paths) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the extra method here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JonRowe , I was thinking the method #get_files_to_run was getting too large, so I wanted to break it up. Maybe that's premature, since nothing else calls #get_files. |
||
files = FlatMap.flat_map(paths_to_check(paths)) do |path| | ||
def get_files(paths) | ||
FlatMap.flat_map(paths_to_check(paths)) do |path| | ||
path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR | ||
File.directory?(path) ? gather_directories(path) : extract_location(path) | ||
end.uniq | ||
end | ||
|
||
def get_files_to_run(paths) | ||
files = get_files(paths) | ||
return files unless only_flag_set? | ||
|
||
return files unless only_failures? | ||
relative_files = files.map { |f| Metadata.relative_path(File.expand_path f) } | ||
intersection = (relative_files & spec_files_with_failures.to_a) | ||
|
||
# If both are set, #fail_if_config_and_cli_options_invalid should have caught it. | ||
case [only_failures?, only_pending?] | ||
in [true, _] | ||
intersection = (relative_files & spec_files_with_failures.to_a) | ||
in [_, true] | ||
intersection = (relative_files & spec_files_with_pending.to_a) | ||
end | ||
|
||
intersection.empty? ? files : intersection | ||
end | ||
|
||
|
@@ -2339,6 +2374,7 @@ def update_pattern_attr(name, value) | |
def clear_values_derived_from_example_status_persistence_file_path | ||
@last_run_statuses = nil | ||
@spec_files_with_failures = nil | ||
@spec_files_with_pending = nil | ||
end | ||
|
||
def configure_group_with(group, module_list, application_method) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -217,6 +217,10 @@ def parser(options) | |
options[:order] ||= 'defined' | ||
end | ||
|
||
parser.on('--only-pending', "Filter to just the examples that were pending the last time they ran.") do | ||
configure_only_pending(options) | ||
end | ||
|
||
parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o| | ||
if options[:pattern] | ||
options[:pattern] += ',' + o | ||
|
@@ -319,5 +323,10 @@ def configure_only_failures(options) | |
options[:only_failures] = true | ||
add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed') | ||
end | ||
|
||
def configure_only_pending(options) | ||
options[:only_pending] = true | ||
add_tag_filter(options, :inclusion_filter, :last_run_status, 'pending') | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These could probably be collapsed to a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JonRowe , as a tradeoff here, I'm supplying three arguments. This accounts for the difference between 'failures' and 'failed'. If the terms were the same like 'pending', then I could just construct the symbol like
My other thought was some sort of constant mapping. |
||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gathering the intersection of files here is where I found it getting messy. Specifically if there were ever to be more flags similar to these set, it might be worth extracting some of this functionality.