diff --git a/features/command_line/order.feature b/features/command_line/order.feature new file mode 100644 index 0000000000..6513bcbb51 --- /dev/null +++ b/features/command_line/order.feature @@ -0,0 +1,119 @@ +Feature: Using the `--order` option + +Use the `--order` option to tell RSpec how to order the files, groups, and +examples. The available ordering schemes are `defined` and `rand`. + +`defined` is the default, which executes groups and examples in the order they +are defined as the spec files are loaded, with the caveat that each group +runs its examples before running its nested example groups, even if the +nested groups are defined before the examples. + +Use `rand` to randomize the order of groups and examples within the groups. +Nested groups are always run from top-level to bottom-level in order to avoid +executing `before(:context)` and `after(:context)` hooks more than once, but the +order of groups at each level is randomized. + +With `rand` you can also specify a seed. + +Use `recently-modified` to run the most recently modified files first. You can +combine it with `--only-failures` to find the most recent failing specs. Note +that `recently-modified` and `rand` are mutually exclusive. + +** Example usage ** + +The `defined` option is only necessary when you have `--order rand` stored in a +config file (e.g. `.rspec`) and you want to override it from the command line. + +
--order defined
+--order rand
+--order rand:123
+--seed 123 # same as --order rand:123
+--order recently-modified
+
+
+Scenario: Default order is `defined`
+ Given a file named "example_spec.rb" with:
+ """ruby
+ RSpec.describe "something" do
+ it "does something" do
+ end
+
+ it "in order" do
+ end
+ end
+ """
+ When I run `rspec example_spec.rb --format documentation`
+ Then the output should contain:
+ """
+ something
+ does something
+ in order
+ """
+
+Scenario: Order can be psuedo randomised (seed used here to fix the ordering for tests)
+ Given a file named "example_spec.rb" with:
+ """ruby
+ RSpec.describe "something" do
+ it "does something" do
+ end
+
+ it "in order" do
+ end
+ end
+ """
+ When I run `rspec example_spec.rb --format documentation --order rand:123`
+ Then the output should contain:
+ """
+ something
+ in order
+ does something
+ """
+
+Scenario: Configure custom ordering
+ Given a file named "example_spec.rb" with:
+ """ruby
+ RSpec.configure do |config|
+ config.register_ordering(:reverse) do |examples|
+ examples.reverse
+ end
+ config.order = :reverse
+ end
+
+ RSpec.describe "something" do
+ it "does something" do
+ end
+
+ it "in order" do
+ end
+ end
+ """
+ When I run `rspec example_spec.rb --format documentation --order reverse`
+ Then the output should contain:
+ """
+ something
+ in order
+ does something
+ """
+
+Scenario: Override order to `defined` when another order is set
+ Given a file named "example_spec.rb" with:
+ """ruby
+ RSpec.configure do |config|
+ config.order = :random
+ config.seed = 123
+ end
+ RSpec.describe "something" do
+ it "does something" do
+ end
+
+ it "in order" do
+ end
+ end
+ """
+ When I run `rspec example_spec.rb --format documentation --order defined`
+ Then the output should contain:
+ """
+ something
+ does something
+ in order
+ """
diff --git a/features/command_line/order.md b/features/command_line/order.md
deleted file mode 100644
index 3946d10a62..0000000000
--- a/features/command_line/order.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Using the `--order` option
-
-Use the `--order` option to tell RSpec how to order the files, groups, and
-examples. The available ordering schemes are `defined` and `rand`.
-
-`defined` is the default, which executes groups and examples in the order they
-are defined as the spec files are loaded, with the caveat that each group
-runs its examples before running its nested example groups, even if the
-nested groups are defined before the examples.
-
-Use `rand` to randomize the order of groups and examples within the groups.
-Nested groups are always run from top-level to bottom-level in order to avoid
-executing `before(:context)` and `after(:context)` hooks more than once, but the
-order of groups at each level is randomized.
-
-With `rand` you can also specify a seed.
-
-Use `recently-modified` to run the most recently modified files first. You can
-combine it with `--only-failures` to find the most recent failing specs. Note
-that `recently-modified` and `rand` are mutually exclusive.
-
-## Example usage
-
-The `defined` option is only necessary when you have `--order rand` stored in a
-config file (e.g. `.rspec`) and you want to override it from the command line.
-
---order defined
---order rand
---order rand:123
---seed 123 # same as --order rand:123
---order recently-modified
-
diff --git a/lib/rspec/core/ordering.rb b/lib/rspec/core/ordering.rb
index d852324dc5..6058a2f244 100644
--- a/lib/rspec/core/ordering.rb
+++ b/lib/rspec/core/ordering.rb
@@ -78,6 +78,30 @@ def order(list)
end
end
+ # @private
+ # A strategy which delays looking up the ordering until needed
+ class Delayed
+ def initialize(registry, name)
+ @registry = registry
+ @name = name
+ end
+
+ def order(list)
+ strategy.order(list)
+ end
+
+ private
+
+ def strategy
+ @strategy ||= lookup_strategy
+ end
+
+ def lookup_strategy
+ raise "Undefined ordering strategy #{@name.inspect}" unless @registry.has_strategy?(@name)
+ @registry.fetch(@name)
+ end
+ end
+
# @private
# Stores the different ordering strategies.
class Registry
@@ -99,6 +123,10 @@ def fetch(name, &fallback)
@strategies.fetch(name, &fallback)
end
+ def has_strategy?(name)
+ @strategies.key?(name)
+ end
+
def register(sym, strategy)
@strategies[sym] = strategy
end
@@ -143,9 +171,20 @@ def order=(type)
:defined
elsif order == 'recently-modified'
:recently_modified
+ else
+ order.to_sym
end
- register_ordering(:global, ordering_registry.fetch(ordering_name)) if ordering_name
+ if ordering_name
+ strategy =
+ if ordering_registry.has_strategy?(ordering_name)
+ ordering_registry.fetch(ordering_name)
+ else
+ Delayed.new(ordering_registry, ordering_name)
+ end
+
+ register_ordering(:global, strategy)
+ end
end
def force(hash)
diff --git a/spec/rspec/core/configuration_spec.rb b/spec/rspec/core/configuration_spec.rb
index bf8ce0db7c..165990f5aa 100644
--- a/spec/rspec/core/configuration_spec.rb
+++ b/spec/rspec/core/configuration_spec.rb
@@ -2602,6 +2602,49 @@ def use_seed_on(registry)
expect(ordering_strategy.order(list)).to eq([1, 2, 3, 4])
end
end
+
+ context 'given a custom ordering strategy' do
+ before do
+ allow(RSpec).to receive_messages(:configuration => config)
+ end
+
+ it 'will lookup a previously registed ordering strategy' do
+ config.register_ordering(:custom_scheme) { |list| list.reverse }
+
+ config.order = :custom_scheme
+
+ strategy = config.ordering_registry.fetch(:global)
+ expect(strategy.order([1, 2, 3, 4])).to eq [4, 3, 2, 1]
+ end
+
+ it 'will defer lookup until running' do
+ config.order = :custom_scheme
+
+ strategy = config.ordering_registry.fetch(:global)
+ expect(strategy).to be_an_instance_of(Ordering::Delayed)
+
+ config.register_ordering(:custom_scheme) { |list| list.reverse }
+ expect(strategy.order([1, 2, 3, 4])).to eq [4, 3, 2, 1]
+ end
+
+ it 'will raise an error if ordering is not present when needed' do
+ config.order = :custom_scheme
+
+ strategy = config.ordering_registry.fetch(:global)
+ expect(strategy).to be_an_instance_of(Ordering::Delayed)
+
+ expect { strategy.order([1, 2, 3, 4]) }.to raise_error("Undefined ordering strategy :custom_scheme")
+ end
+
+ it 'will lookup schemes as symbols even if given as strings' do
+ config.order = 'custom_scheme'
+
+ config.register_ordering(:custom_scheme) { |list| list.reverse }
+
+ strategy = config.ordering_registry.fetch(:global)
+ expect(strategy.order([1, 2, 3, 4])).to eq [4, 3, 2, 1]
+ end
+ end
end
describe "#register_ordering" do
diff --git a/spec/rspec/core/ordering_spec.rb b/spec/rspec/core/ordering_spec.rb
index d68a628e87..40ce38ed94 100644
--- a/spec/rspec/core/ordering_spec.rb
+++ b/spec/rspec/core/ordering_spec.rb
@@ -104,6 +104,18 @@ def order_with(seed)
end
end
+ RSpec.describe Delayed do
+ let(:registry) { Registry.new(Configuration.new) }
+
+ it 'looks up a strategy to order the list later on' do
+ strategy = Delayed.new(registry, :reverse)
+ expect { strategy.order([1, 2, 3, 4]) }.to raise_error("Undefined ordering strategy :reverse")
+
+ registry.register(:reverse, Custom.new(proc { |list| list.reverse }))
+ expect(strategy.order([1, 2, 3, 4])).to eq([4, 3, 2, 1])
+ end
+ end
+
RSpec.describe Registry do
let(:configuration) { Configuration.new }
subject(:registry) { Registry.new(configuration) }
@@ -144,6 +156,15 @@ def order_with(seed)
end
end
end
+
+ describe "#has_strategy?(name)" do
+ it "returns true if the strategy was registered" do
+ expect {
+ registry.register(:reverse, Custom.new(proc { |list| list.reverse }))
+ }.to change { registry.has_strategy?(:reverse) }.from(false).to(true)
+ end
+ end
+
end
end
end