rspec-core 3.0.4 → 3.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +2 -1
  5. data/Changelog.md +888 -2
  6. data/{License.txt → LICENSE.md} +6 -5
  7. data/README.md +165 -24
  8. data/lib/rspec/autorun.rb +1 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +19 -20
  10. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  11. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  12. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  13. data/lib/rspec/core/bisect/server.rb +61 -0
  14. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  15. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  16. data/lib/rspec/core/bisect/utilities.rb +69 -0
  17. data/lib/rspec/core/configuration.rb +1287 -246
  18. data/lib/rspec/core/configuration_options.rb +95 -35
  19. data/lib/rspec/core/did_you_mean.rb +46 -0
  20. data/lib/rspec/core/drb.rb +21 -12
  21. data/lib/rspec/core/dsl.rb +10 -6
  22. data/lib/rspec/core/example.rb +305 -113
  23. data/lib/rspec/core/example_group.rb +431 -223
  24. data/lib/rspec/core/example_status_persister.rb +235 -0
  25. data/lib/rspec/core/filter_manager.rb +86 -115
  26. data/lib/rspec/core/flat_map.rb +6 -4
  27. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  28. data/lib/rspec/core/formatters/base_formatter.rb +14 -116
  29. data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
  30. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  31. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  32. data/lib/rspec/core/formatters/console_codes.rb +29 -18
  33. data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
  34. data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
  35. data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
  36. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  37. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  38. data/lib/rspec/core/formatters/helpers.rb +45 -15
  39. data/lib/rspec/core/formatters/html_formatter.rb +33 -28
  40. data/lib/rspec/core/formatters/html_printer.rb +30 -20
  41. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  42. data/lib/rspec/core/formatters/json_formatter.rb +18 -9
  43. data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
  44. data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
  45. data/lib/rspec/core/formatters/protocol.rb +182 -0
  46. data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
  47. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  48. data/lib/rspec/core/formatters.rb +81 -41
  49. data/lib/rspec/core/hooks.rb +314 -244
  50. data/lib/rspec/core/invocations.rb +87 -0
  51. data/lib/rspec/core/memoized_helpers.rb +161 -51
  52. data/lib/rspec/core/metadata.rb +132 -61
  53. data/lib/rspec/core/metadata_filter.rb +224 -64
  54. data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
  55. data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
  56. data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
  57. data/lib/rspec/core/mocking_adapters/null.rb +2 -0
  58. data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
  59. data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
  60. data/lib/rspec/core/notifications.rb +192 -206
  61. data/lib/rspec/core/option_parser.rb +174 -69
  62. data/lib/rspec/core/ordering.rb +48 -35
  63. data/lib/rspec/core/output_wrapper.rb +29 -0
  64. data/lib/rspec/core/pending.rb +25 -33
  65. data/lib/rspec/core/profiler.rb +34 -0
  66. data/lib/rspec/core/project_initializer/.rspec +0 -2
  67. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
  68. data/lib/rspec/core/project_initializer.rb +5 -3
  69. data/lib/rspec/core/rake_task.rb +99 -55
  70. data/lib/rspec/core/reporter.rb +128 -15
  71. data/lib/rspec/core/ruby_project.rb +14 -6
  72. data/lib/rspec/core/runner.rb +96 -45
  73. data/lib/rspec/core/sandbox.rb +37 -0
  74. data/lib/rspec/core/set.rb +54 -0
  75. data/lib/rspec/core/shared_example_group.rb +133 -43
  76. data/lib/rspec/core/shell_escape.rb +49 -0
  77. data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
  78. data/lib/rspec/core/version.rb +1 -1
  79. data/lib/rspec/core/warnings.rb +6 -6
  80. data/lib/rspec/core/world.rb +172 -68
  81. data/lib/rspec/core.rb +66 -21
  82. data.tar.gz.sig +0 -0
  83. metadata +93 -69
  84. metadata.gz.sig +0 -0
  85. data/lib/rspec/core/backport_random.rb +0 -336
@@ -1,15 +1,12 @@
1
1
  # This is borrowed (slightly modified) from Scott Taylor's
2
2
  # project_path project:
3
3
  # http://github.com/smtlaissezfaire/project_path
4
-
5
- require 'pathname'
6
-
7
4
  module RSpec
8
5
  module Core
9
6
  # @private
10
7
  module RubyProject
11
8
  def add_to_load_path(*dirs)
12
- dirs.map {|dir| add_dir_to_load_path(File.join(root, dir))}
9
+ dirs.each { |dir| add_dir_to_load_path(File.join(root, dir)) }
13
10
  end
14
11
 
15
12
  def add_dir_to_load_path(dir)
@@ -25,12 +22,23 @@ module RSpec
25
22
  end
26
23
 
27
24
  def find_first_parent_containing(dir)
28
- ascend_until {|path| File.exist?(File.join(path, dir))}
25
+ ascend_until { |path| File.exist?(File.join(path, dir)) }
29
26
  end
30
27
 
31
28
  def ascend_until
32
- Pathname(File.expand_path('.')).ascend do |path|
29
+ fs = File::SEPARATOR
30
+ escaped_slash = "\\#{fs}"
31
+ special = "_RSPEC_ESCAPED_SLASH_"
32
+ project_path = File.expand_path(".")
33
+ parts = project_path.gsub(escaped_slash, special).squeeze(fs).split(fs).map do |x|
34
+ x.gsub(special, escaped_slash)
35
+ end
36
+
37
+ until parts.empty?
38
+ path = parts.join(fs)
39
+ path = fs if path == ""
33
40
  return path if yield(path)
41
+ parts.pop
34
42
  end
35
43
  end
36
44
 
@@ -2,6 +2,9 @@ module RSpec
2
2
  module Core
3
3
  # Provides the main entry point to run a suite of RSpec examples.
4
4
  class Runner
5
+ # @attr_reader
6
+ # @private
7
+ attr_reader :options, :configuration, :world
5
8
 
6
9
  # Register an `at_exit` hook that runs the suite when the process exits.
7
10
  #
@@ -18,21 +21,25 @@ module RSpec
18
21
  return
19
22
  end
20
23
 
21
- at_exit do
22
- # Don't bother running any specs and just let the program terminate
23
- # if we got here due to an unrescued exception (anything other than
24
- # SystemExit, which is raised when somebody calls Kernel#exit).
25
- next unless $!.nil? || $!.kind_of?(SystemExit)
26
-
27
- # We got here because either the end of the program was reached or
28
- # somebody called Kernel#exit. Run the specs and then override any
29
- # existing exit status with RSpec's exit status if any specs failed.
30
- invoke
31
- end
24
+ at_exit { perform_at_exit }
32
25
  @installed_at_exit = true
33
26
  end
34
27
 
35
- # Runs the suite of specs and exits the process with an appropriate exit code.
28
+ # @private
29
+ def self.perform_at_exit
30
+ # Don't bother running any specs and just let the program terminate
31
+ # if we got here due to an unrescued exception (anything other than
32
+ # SystemExit, which is raised when somebody calls Kernel#exit).
33
+ return unless $!.nil? || $!.is_a?(SystemExit)
34
+
35
+ # We got here because either the end of the program was reached or
36
+ # somebody called Kernel#exit. Run the specs and then override any
37
+ # existing exit status with RSpec's exit status if any specs failed.
38
+ invoke
39
+ end
40
+
41
+ # Runs the suite of specs and exits the process with an appropriate exit
42
+ # code.
36
43
  def self.invoke
37
44
  disable_autorun!
38
45
  status = run(ARGV, $stderr, $stdout).to_i
@@ -58,14 +65,8 @@ module RSpec
58
65
  trap_interrupt
59
66
  options = ConfigurationOptions.new(args)
60
67
 
61
- if options.options[:drb]
62
- require 'rspec/core/drb'
63
- begin
64
- DRbRunner.new(options).run(err, out)
65
- rescue DRb::DRbConnError
66
- err.puts "No DRb server is running. Running in local process instead ..."
67
- new(options).run(err, out)
68
- end
68
+ if options.options[:runner]
69
+ options.options[:runner].call(options, err, out)
69
70
  else
70
71
  new(options).run(err, out)
71
72
  end
@@ -83,7 +84,11 @@ module RSpec
83
84
  # @param out [IO] output stream
84
85
  def run(err, out)
85
86
  setup(err, out)
86
- run_specs(@world.ordered_example_groups)
87
+ return @configuration.reporter.exit_early(exit_code) if RSpec.world.wants_to_quit
88
+
89
+ run_specs(@world.ordered_example_groups).tap do
90
+ persist_example_statuses
91
+ end
87
92
  end
88
93
 
89
94
  # Wires together the various configuration objects and state holders.
@@ -91,10 +96,11 @@ module RSpec
91
96
  # @param err [IO] error stream
92
97
  # @param out [IO] output stream
93
98
  def setup(err, out)
94
- @configuration.error_stream = err
95
- @configuration.output_stream = out if @configuration.output_stream == $stdout
96
- @options.configure(@configuration)
99
+ configure(err, out)
100
+ return if RSpec.world.wants_to_quit
101
+
97
102
  @configuration.load_spec_files
103
+ ensure
98
104
  @world.announce_filters
99
105
  end
100
106
 
@@ -105,15 +111,25 @@ module RSpec
105
111
  # or the configured failure exit code (1 by default) if specs
106
112
  # failed.
107
113
  def run_specs(example_groups)
108
- @configuration.reporter.report(@world.example_count(example_groups)) do |reporter|
109
- begin
110
- hook_context = SuiteHookContext.new
111
- @configuration.hooks.run(:before, :suite, hook_context)
112
- example_groups.map { |g| g.run(reporter) }.all? ? 0 : @configuration.failure_exit_code
113
- ensure
114
- @configuration.hooks.run(:after, :suite, hook_context)
114
+ examples_count = @world.example_count(example_groups)
115
+ examples_passed = @configuration.reporter.report(examples_count) do |reporter|
116
+ @configuration.with_suite_hooks do
117
+ if examples_count == 0 && @configuration.fail_if_no_examples
118
+ return @configuration.failure_exit_code
119
+ end
120
+
121
+ example_groups.map { |g| g.run(reporter) }.all?
115
122
  end
116
123
  end
124
+
125
+ exit_code(examples_passed)
126
+ end
127
+
128
+ # @private
129
+ def configure(err, out)
130
+ @configuration.error_stream = err
131
+ @configuration.output_stream = out if @configuration.output_stream == $stdout
132
+ @options.configure(@configuration)
117
133
  end
118
134
 
119
135
  # @private
@@ -133,29 +149,64 @@ module RSpec
133
149
 
134
150
  # @private
135
151
  def self.running_in_drb?
136
- begin
137
- if defined?(DRb) && DRb.current_server
138
- require 'socket'
139
- require 'uri'
152
+ return false unless defined?(DRb)
140
153
 
141
- local_ipv4 = IPSocket.getaddress(Socket.gethostname)
154
+ server = begin
155
+ DRb.current_server
156
+ rescue DRb::DRbServerNotFound
157
+ return false
158
+ end
142
159
 
143
- local_drb = ["127.0.0.1", "localhost", local_ipv4].any? { |addr| addr == URI(DRb.current_server.uri).host }
144
- end
145
- rescue DRb::DRbServerNotFound
146
- ensure
147
- return local_drb || false
148
- end
160
+ return false unless server && server.alive?
161
+
162
+ require 'socket'
163
+ require 'uri'
164
+
165
+ local_ipv4 = begin
166
+ IPSocket.getaddress(Socket.gethostname)
167
+ rescue SocketError
168
+ return false
169
+ end
170
+
171
+ ["127.0.0.1", "localhost", local_ipv4].any? { |addr| addr == URI(DRb.current_server.uri).host }
149
172
  end
150
173
 
151
174
  # @private
152
175
  def self.trap_interrupt
153
- trap('INT') do
154
- exit!(1) if RSpec.world.wants_to_quit
176
+ trap('INT') { handle_interrupt }
177
+ end
178
+
179
+ # @private
180
+ def self.handle_interrupt
181
+ if RSpec.world.wants_to_quit
182
+ exit!(1)
183
+ else
155
184
  RSpec.world.wants_to_quit = true
156
- STDERR.puts "\nExiting... Interrupt again to exit immediately."
185
+ $stderr.puts "\nRSpec is shutting down and will print the summary report... Interrupt again to force quit."
157
186
  end
158
187
  end
188
+
189
+ # @private
190
+ def exit_code(examples_passed=false)
191
+ return @configuration.error_exit_code || @configuration.failure_exit_code if @world.non_example_failure
192
+ return @configuration.failure_exit_code unless examples_passed
193
+
194
+ 0
195
+ end
196
+
197
+ private
198
+
199
+ def persist_example_statuses
200
+ return if @configuration.dry_run
201
+ return unless (path = @configuration.example_status_persistence_file_path)
202
+
203
+ ExampleStatusPersister.persist(@world.all_examples, path)
204
+ rescue SystemCallError => e
205
+ RSpec.warning "Could not write example statuses to #{path} (configured as " \
206
+ "`config.example_status_persistence_file_path`) due to a " \
207
+ "system error: #{e.inspect}. Please check that the config " \
208
+ "option is set to an accessible, valid file path", :call_site => nil
209
+ end
159
210
  end
160
211
  end
161
212
  end
@@ -0,0 +1,37 @@
1
+ module RSpec
2
+ module Core
3
+ # A sandbox isolates the enclosed code into an environment that looks 'new'
4
+ # meaning globally accessed objects are reset for the duration of the
5
+ # sandbox.
6
+ #
7
+ # @note This module is not normally available. You must require
8
+ # `rspec/core/sandbox` to load it.
9
+ module Sandbox
10
+ # Execute a provided block with RSpec global objects (configuration,
11
+ # world) reset. This is used to test RSpec with RSpec.
12
+ #
13
+ # When calling this the configuration is passed into the provided block.
14
+ # Use this to set custom configs for your sandboxed examples.
15
+ #
16
+ # ```
17
+ # Sandbox.sandboxed do |config|
18
+ # config.before(:context) { RSpec.current_example = nil }
19
+ # end
20
+ # ```
21
+ def self.sandboxed
22
+ orig_config = RSpec.configuration
23
+ orig_world = RSpec.world
24
+ orig_example = RSpec.current_example
25
+
26
+ RSpec.configuration = RSpec::Core::Configuration.new
27
+ RSpec.world = RSpec::Core::World.new(RSpec.configuration)
28
+
29
+ yield RSpec.configuration
30
+ ensure
31
+ RSpec.configuration = orig_config
32
+ RSpec.world = orig_world
33
+ RSpec.current_example = orig_example
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ #
5
+ # We use this to replace `::Set` so we can have the advantage of
6
+ # constant time key lookups for unique arrays but without the
7
+ # potential to pollute a developers environment with an extra
8
+ # piece of the stdlib. This helps to prevent false positive
9
+ # builds.
10
+ #
11
+ class Set
12
+ include Enumerable
13
+
14
+ def initialize(array=[])
15
+ @values = {}
16
+ merge(array)
17
+ end
18
+
19
+ def empty?
20
+ @values.empty?
21
+ end
22
+
23
+ def <<(key)
24
+ @values[key] = true
25
+ self
26
+ end
27
+
28
+ def delete(key)
29
+ @values.delete(key)
30
+ end
31
+
32
+ def each(&block)
33
+ @values.keys.each(&block)
34
+ self
35
+ end
36
+
37
+ def include?(key)
38
+ @values.key?(key)
39
+ end
40
+
41
+ def merge(values)
42
+ values.each do |key|
43
+ @values[key] = true
44
+ end
45
+ self
46
+ end
47
+
48
+ def clear
49
+ @values.clear
50
+ self
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,5 +1,46 @@
1
+ RSpec::Support.require_rspec_support "with_keywords_when_needed"
2
+
1
3
  module RSpec
2
4
  module Core
5
+ # Represents some functionality that is shared with multiple example groups.
6
+ # The functionality is defined by the provided block, which is lazily
7
+ # eval'd when the `SharedExampleGroupModule` instance is included in an example
8
+ # group.
9
+ class SharedExampleGroupModule < Module
10
+ # @private
11
+ attr_reader :definition
12
+
13
+ def initialize(description, definition, metadata)
14
+ @description = description
15
+ @definition = definition
16
+ @metadata = metadata
17
+ end
18
+
19
+ # Provides a human-readable representation of this module.
20
+ def inspect
21
+ "#<#{self.class.name} #{@description.inspect}>"
22
+ end
23
+ alias to_s inspect
24
+
25
+ # Ruby callback for when a module is included in another module is class.
26
+ # Our definition evaluates the shared group block in the context of the
27
+ # including example group.
28
+ def included(klass)
29
+ inclusion_line = klass.metadata[:location]
30
+ include_in klass, inclusion_line, [], nil
31
+ end
32
+
33
+ # @private
34
+ def include_in(klass, inclusion_line, args, customization_block)
35
+ klass.update_inherited_metadata(@metadata) unless @metadata.empty?
36
+
37
+ SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
38
+ RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *args, &@definition)
39
+ klass.class_exec(&customization_block) if customization_block
40
+ end
41
+ end
42
+ end
43
+
3
44
  # Shared example groups let you define common context and/or common
4
45
  # examples that you wish to use in multiple example groups.
5
46
  #
@@ -15,16 +56,15 @@ module RSpec
15
56
  # groups defined at the top level can be included from any example group.
16
57
  module SharedExampleGroup
17
58
  # @overload shared_examples(name, &block)
18
- # @param name [String, Symbol, Module] identifer to use when looking up this shared group
59
+ # @param name [String, Symbol, Module] identifer to use when looking up
60
+ # this shared group
19
61
  # @param block The block to be eval'd
20
62
  # @overload shared_examples(name, metadata, &block)
21
- # @param name [String, Symbol, Module] identifer to use when looking up this shared group
22
- # @param metadata [Array<Symbol>, Hash] metadata to attach to this group; any example group
23
- # with matching metadata will automatically include this shared example group.
24
- # @param block The block to be eval'd
25
- # @overload shared_examples(metadata, &block)
26
- # @param metadata [Array<Symbol>, Hash] metadata to attach to this group; any example group
27
- # with matching metadata will automatically include this shared example group.
63
+ # @param name [String, Symbol, Module] identifer to use when looking up
64
+ # this shared group
65
+ # @param metadata [Array<Symbol>, Hash] metadata to attach to this
66
+ # group; any example group or example with matching metadata will
67
+ # automatically include this shared example group.
28
68
  # @param block The block to be eval'd
29
69
  #
30
70
  # Stores the block for later use. The block will be evaluated
@@ -38,7 +78,7 @@ module RSpec
38
78
  # end
39
79
  # end
40
80
  #
41
- # describe Account do
81
+ # RSpec.describe Account do
42
82
  # it_behaves_like "auditable" do
43
83
  # let(:auditable) { Account.new }
44
84
  # end
@@ -49,9 +89,9 @@ module RSpec
49
89
  # @see ExampleGroup.include_context
50
90
  def shared_examples(name, *args, &block)
51
91
  top_level = self == ExampleGroup
52
- if top_level && RSpec.thread_local_metadata[:in_example_group]
53
- raise "Creating isolated shared examples from within a context is " +
54
- "not allowed. Remove `RSpec.` prefix or move this to a " +
92
+ if top_level && RSpec::Support.thread_local_data[:in_example_group]
93
+ raise "Creating isolated shared examples from within a context is " \
94
+ "not allowed. Remove `RSpec.` prefix or move this to a " \
55
95
  "top-level scope."
56
96
  end
57
97
 
@@ -62,7 +102,7 @@ module RSpec
62
102
 
63
103
  # @api private
64
104
  #
65
- # Shared examples top level DSL
105
+ # Shared examples top level DSL.
66
106
  module TopLevelDSL
67
107
  # @private
68
108
  def self.definitions
@@ -82,7 +122,7 @@ module RSpec
82
122
 
83
123
  # @api private
84
124
  #
85
- # Adds the top level DSL methods to Module and the top level binding
125
+ # Adds the top level DSL methods to Module and the top level binding.
86
126
  def self.expose_globally!
87
127
  return if exposed_globally?
88
128
  Core::DSL.change_global_dsl(&definitions)
@@ -91,7 +131,7 @@ module RSpec
91
131
 
92
132
  # @api private
93
133
  #
94
- # Removes the top level DSL methods to Module and the top level binding
134
+ # Removes the top level DSL methods to Module and the top level binding.
95
135
  def self.remove_globally!
96
136
  return unless exposed_globally?
97
137
 
@@ -103,28 +143,32 @@ module RSpec
103
143
 
104
144
  @exposed_globally = false
105
145
  end
106
-
107
146
  end
108
147
 
109
148
  # @private
110
149
  class Registry
111
150
  def add(context, name, *metadata_args, &block)
112
- ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
151
+ unless block
152
+ RSpec.warning "Shared example group #{name} was defined without a "\
153
+ "block and will have no effect. Please define a "\
154
+ "block or remove the definition."
155
+ end
113
156
 
114
- if valid_name?(name)
115
- warn_if_key_taken context, name, block
116
- shared_example_groups[context][name] = block
117
- else
118
- metadata_args.unshift name
157
+ if RSpec.configuration.shared_context_metadata_behavior == :trigger_inclusion
158
+ return legacy_add(context, name, *metadata_args, &block)
119
159
  end
120
160
 
121
- unless metadata_args.empty?
122
- mod = Module.new
123
- (class << mod; self; end).__send__(:define_method, :included) do |host|
124
- host.class_exec(&block)
125
- end
126
- RSpec.configuration.include mod, *metadata_args
161
+ unless valid_name?(name)
162
+ raise ArgumentError, "Shared example group names can only be a string, " \
163
+ "symbol or module but got: #{name.inspect}"
127
164
  end
165
+
166
+ ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
167
+ warn_if_key_taken context, name, block
168
+
169
+ metadata = Metadata.build_hash_from(metadata_args)
170
+ shared_module = SharedExampleGroupModule.new(name, block, metadata)
171
+ shared_example_groups[context][name] = shared_module
128
172
  end
129
173
 
130
174
  def find(lookup_contexts, name)
@@ -138,40 +182,86 @@ module RSpec
138
182
 
139
183
  private
140
184
 
185
+ # TODO: remove this in RSpec 4. This exists only to support
186
+ # `config.shared_context_metadata_behavior == :trigger_inclusion`,
187
+ # the legacy behavior of shared context metadata, which we do
188
+ # not want to support in RSpec 4.
189
+ def legacy_add(context, name, *metadata_args, &block)
190
+ ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }
191
+ shared_module = SharedExampleGroupModule.new(name, block, {})
192
+
193
+ if valid_name?(name)
194
+ warn_if_key_taken context, name, block
195
+ shared_example_groups[context][name] = shared_module
196
+ else
197
+ metadata_args.unshift name
198
+ end
199
+
200
+ return if metadata_args.empty?
201
+ RSpec.configuration.include shared_module, *metadata_args
202
+ end
203
+
141
204
  def shared_example_groups
142
205
  @shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} }
143
206
  end
144
207
 
145
208
  def valid_name?(candidate)
146
209
  case candidate
147
- when String, Symbol, Module then true
148
- else false
210
+ when String, Symbol, Module then true
211
+ else false
149
212
  end
150
213
  end
151
214
 
152
215
  def warn_if_key_taken(context, key, new_block)
153
- return unless existing_block = shared_example_groups[context][key]
154
-
155
- RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
156
- |WARNING: Shared example group '#{key}' has been previously defined at:
157
- | #{formatted_location existing_block}
158
- |...and you are now defining it at:
159
- | #{formatted_location new_block}
160
- |The new definition will overwrite the original one.
161
- WARNING
216
+ existing_module = shared_example_groups[context][key]
217
+ return unless existing_module
218
+
219
+ old_definition_location = formatted_location existing_module.definition
220
+ new_definition_location = formatted_location new_block
221
+ loaded_spec_files = RSpec.configuration.loaded_spec_files
222
+
223
+ if loaded_spec_files.include?(new_definition_location) && old_definition_location == new_definition_location
224
+ RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
225
+ |WARNING: Your shared example group, '#{key}', defined at:
226
+ | #{old_definition_location}
227
+ |was automatically loaded by RSpec because the file name
228
+ |matches the configured autoloading pattern (#{RSpec.configuration.pattern}),
229
+ |and is also being required from somewhere else. To fix this
230
+ |warning, either rename the file to not match the pattern, or
231
+ |do not explicitly require the file.
232
+ WARNING
233
+ else
234
+ RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
235
+ |WARNING: Shared example group '#{key}' has been previously defined at:
236
+ | #{old_definition_location}
237
+ |...and you are now defining it at:
238
+ | #{new_definition_location}
239
+ |The new definition will overwrite the original one.
240
+ WARNING
241
+ end
162
242
  end
163
243
 
164
- def formatted_location(block)
165
- block.source_location.join ":"
244
+ if RUBY_VERSION.to_f >= 1.9
245
+ def formatted_location(block)
246
+ block.source_location.join(":")
247
+ end
248
+ else # 1.8.7
249
+ # :nocov:
250
+ def formatted_location(block)
251
+ block.source_location.join(":").gsub(/:in.*$/, '')
252
+ end
253
+ # :nocov:
166
254
  end
167
255
 
168
256
  if Proc.method_defined?(:source_location)
169
- def ensure_block_has_source_location(block); end
257
+ def ensure_block_has_source_location(_block); end
170
258
  else # for 1.8.7
259
+ # :nocov:
171
260
  def ensure_block_has_source_location(block)
172
261
  source_location = yield.split(':')
173
- block.extend Module.new { define_method(:source_location) { source_location } }
262
+ block.extend(Module.new { define_method(:source_location) { source_location } })
174
263
  end
264
+ # :nocov:
175
265
  end
176
266
  end
177
267
  end
@@ -0,0 +1,49 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ # Deals with the fact that `shellwords` only works on POSIX systems.
5
+ module ShellEscape
6
+ module_function
7
+
8
+ def quote(argument)
9
+ "'#{argument.to_s.gsub("'", "\\\\'")}'"
10
+ end
11
+
12
+ if RSpec::Support::OS.windows?
13
+ # :nocov:
14
+ alias escape quote
15
+ # :nocov:
16
+ else
17
+ require 'shellwords'
18
+
19
+ def escape(shell_command)
20
+ Shellwords.escape(shell_command.to_s)
21
+ end
22
+ end
23
+
24
+ # Known shells that require quoting: zsh, csh, tcsh.
25
+ #
26
+ # Feel free to add other shells to this list that are known to
27
+ # allow `rspec ./some_spec.rb[1:1]` syntax without quoting the id.
28
+ #
29
+ # @private
30
+ SHELLS_ALLOWING_UNQUOTED_IDS = %w[ bash ksh fish ]
31
+
32
+ def conditionally_quote(id)
33
+ return id if shell_allows_unquoted_ids?
34
+ quote(id)
35
+ end
36
+
37
+ def shell_allows_unquoted_ids?
38
+ # Note: ENV['SHELL'] isn't necessarily the shell the user is currently running.
39
+ # According to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html:
40
+ # "This variable shall represent a pathname of the user's preferred command language interpreter."
41
+ #
42
+ # It's the best we can easily do, though. We err on the side of safety (quoting
43
+ # the id when not actually needed) so it's not a big deal if the user is actually
44
+ # using a different shell.
45
+ SHELLS_ALLOWING_UNQUOTED_IDS.include?(ENV['SHELL'].to_s.split('/').last)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -16,14 +16,14 @@ module RSpec
16
16
  # adding a shim for the new updates. Thus instead of checking on the
17
17
  # RUBY_VERSION we need to check ancestors.
18
18
  begin
19
- # MiniTest is 4.x
20
- # Minitest is 5.x
19
+ # MiniTest is 4.x.
20
+ # Minitest is 5.x.
21
21
  if ancestors.include?(::Minitest::Assertions)
22
22
  require 'rspec/core/minitest_assertions_adapter'
23
23
  include ::RSpec::Core::MinitestAssertionsAdapter
24
24
  end
25
- rescue NameError => _ignored
26
- # No-op. Minitest 5.x was not loaded
25
+ rescue NameError
26
+ # No-op. Minitest 5.x was not loaded.
27
27
  end
28
28
  end
29
29
  end
@@ -3,7 +3,7 @@ module RSpec
3
3
  # Version information for RSpec Core.
4
4
  module Version
5
5
  # Current version of RSpec Core, in semantic versioning format.
6
- STRING = '3.0.4'
6
+ STRING = '3.12.2'
7
7
  end
8
8
  end
9
9
  end