rspec-core 3.0.4 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
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