rspec-core 3.8.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 (84) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.document +5 -0
  5. data/.yardopts +8 -0
  6. data/Changelog.md +2243 -0
  7. data/LICENSE.md +26 -0
  8. data/README.md +384 -0
  9. data/exe/rspec +4 -0
  10. data/lib/rspec/autorun.rb +3 -0
  11. data/lib/rspec/core.rb +185 -0
  12. data/lib/rspec/core/backtrace_formatter.rb +65 -0
  13. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  14. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  15. data/lib/rspec/core/bisect/fork_runner.rb +134 -0
  16. data/lib/rspec/core/bisect/server.rb +61 -0
  17. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  18. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  19. data/lib/rspec/core/bisect/utilities.rb +58 -0
  20. data/lib/rspec/core/configuration.rb +2308 -0
  21. data/lib/rspec/core/configuration_options.rb +233 -0
  22. data/lib/rspec/core/drb.rb +113 -0
  23. data/lib/rspec/core/dsl.rb +98 -0
  24. data/lib/rspec/core/example.rb +656 -0
  25. data/lib/rspec/core/example_group.rb +889 -0
  26. data/lib/rspec/core/example_status_persister.rb +235 -0
  27. data/lib/rspec/core/filter_manager.rb +231 -0
  28. data/lib/rspec/core/flat_map.rb +20 -0
  29. data/lib/rspec/core/formatters.rb +269 -0
  30. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  31. data/lib/rspec/core/formatters/base_formatter.rb +70 -0
  32. data/lib/rspec/core/formatters/base_text_formatter.rb +75 -0
  33. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  34. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  35. data/lib/rspec/core/formatters/console_codes.rb +68 -0
  36. data/lib/rspec/core/formatters/deprecation_formatter.rb +223 -0
  37. data/lib/rspec/core/formatters/documentation_formatter.rb +70 -0
  38. data/lib/rspec/core/formatters/exception_presenter.rb +508 -0
  39. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  40. data/lib/rspec/core/formatters/helpers.rb +110 -0
  41. data/lib/rspec/core/formatters/html_formatter.rb +153 -0
  42. data/lib/rspec/core/formatters/html_printer.rb +414 -0
  43. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  44. data/lib/rspec/core/formatters/json_formatter.rb +102 -0
  45. data/lib/rspec/core/formatters/profile_formatter.rb +68 -0
  46. data/lib/rspec/core/formatters/progress_formatter.rb +29 -0
  47. data/lib/rspec/core/formatters/protocol.rb +182 -0
  48. data/lib/rspec/core/formatters/snippet_extractor.rb +134 -0
  49. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  50. data/lib/rspec/core/hooks.rb +624 -0
  51. data/lib/rspec/core/invocations.rb +87 -0
  52. data/lib/rspec/core/memoized_helpers.rb +554 -0
  53. data/lib/rspec/core/metadata.rb +498 -0
  54. data/lib/rspec/core/metadata_filter.rb +255 -0
  55. data/lib/rspec/core/minitest_assertions_adapter.rb +31 -0
  56. data/lib/rspec/core/mocking_adapters/flexmock.rb +31 -0
  57. data/lib/rspec/core/mocking_adapters/mocha.rb +57 -0
  58. data/lib/rspec/core/mocking_adapters/null.rb +14 -0
  59. data/lib/rspec/core/mocking_adapters/rr.rb +31 -0
  60. data/lib/rspec/core/mocking_adapters/rspec.rb +32 -0
  61. data/lib/rspec/core/notifications.rb +521 -0
  62. data/lib/rspec/core/option_parser.rb +309 -0
  63. data/lib/rspec/core/ordering.rb +158 -0
  64. data/lib/rspec/core/output_wrapper.rb +29 -0
  65. data/lib/rspec/core/pending.rb +165 -0
  66. data/lib/rspec/core/profiler.rb +34 -0
  67. data/lib/rspec/core/project_initializer.rb +48 -0
  68. data/lib/rspec/core/project_initializer/.rspec +1 -0
  69. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +100 -0
  70. data/lib/rspec/core/rake_task.rb +168 -0
  71. data/lib/rspec/core/reporter.rb +257 -0
  72. data/lib/rspec/core/ruby_project.rb +53 -0
  73. data/lib/rspec/core/runner.rb +199 -0
  74. data/lib/rspec/core/sandbox.rb +37 -0
  75. data/lib/rspec/core/set.rb +54 -0
  76. data/lib/rspec/core/shared_context.rb +55 -0
  77. data/lib/rspec/core/shared_example_group.rb +269 -0
  78. data/lib/rspec/core/shell_escape.rb +49 -0
  79. data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
  80. data/lib/rspec/core/version.rb +9 -0
  81. data/lib/rspec/core/warnings.rb +40 -0
  82. data/lib/rspec/core/world.rb +275 -0
  83. metadata +292 -0
  84. metadata.gz.sig +0 -0
@@ -0,0 +1,233 @@
1
+ require 'erb'
2
+ require 'shellwords'
3
+
4
+ module RSpec
5
+ module Core
6
+ # Responsible for utilizing externally provided configuration options,
7
+ # whether via the command line, `.rspec`, `~/.rspec`,
8
+ # `$XDG_CONFIG_HOME/rspec/options`, `.rspec-local` or a custom options
9
+ # file.
10
+ class ConfigurationOptions
11
+ # @param args [Array<String>] command line arguments
12
+ def initialize(args)
13
+ @args = args.dup
14
+ organize_options
15
+ end
16
+
17
+ # Updates the provided {Configuration} instance based on the provided
18
+ # external configuration options.
19
+ #
20
+ # @param config [Configuration] the configuration instance to update
21
+ def configure(config)
22
+ process_options_into config
23
+ configure_filter_manager config.filter_manager
24
+ load_formatters_into config
25
+ end
26
+
27
+ # @api private
28
+ # Updates the provided {FilterManager} based on the filter options.
29
+ # @param filter_manager [FilterManager] instance to update
30
+ def configure_filter_manager(filter_manager)
31
+ @filter_manager_options.each do |command, value|
32
+ filter_manager.__send__ command, value
33
+ end
34
+ end
35
+
36
+ # @return [Hash] the final merged options, drawn from all external sources
37
+ attr_reader :options
38
+
39
+ # @return [Array<String>] the original command-line arguments
40
+ attr_reader :args
41
+
42
+ private
43
+
44
+ def organize_options
45
+ @filter_manager_options = []
46
+
47
+ @options = (file_options << command_line_options << env_options).each do |opts|
48
+ @filter_manager_options << [:include, opts.delete(:inclusion_filter)] if opts.key?(:inclusion_filter)
49
+ @filter_manager_options << [:exclude, opts.delete(:exclusion_filter)] if opts.key?(:exclusion_filter)
50
+ end
51
+
52
+ @options = @options.inject(:libs => [], :requires => []) do |hash, opts|
53
+ hash.merge(opts) do |key, oldval, newval|
54
+ [:libs, :requires].include?(key) ? oldval + newval : newval
55
+ end
56
+ end
57
+ end
58
+
59
+ UNFORCED_OPTIONS = Set.new([
60
+ :requires, :profile, :drb, :libs, :files_or_directories_to_run,
61
+ :full_description, :full_backtrace, :tty
62
+ ])
63
+
64
+ UNPROCESSABLE_OPTIONS = Set.new([:formatters])
65
+
66
+ def force?(key)
67
+ !UNFORCED_OPTIONS.include?(key)
68
+ end
69
+
70
+ def order(keys)
71
+ OPTIONS_ORDER.reverse_each do |key|
72
+ keys.unshift(key) if keys.delete(key)
73
+ end
74
+ keys
75
+ end
76
+
77
+ OPTIONS_ORDER = [
78
+ # It's important to set this before anything that might issue a
79
+ # deprecation (or otherwise access the reporter).
80
+ :deprecation_stream,
81
+
82
+ # load paths depend on nothing, but must be set before `requires`
83
+ # to support load-path-relative requires.
84
+ :libs,
85
+
86
+ # `files_or_directories_to_run` uses `default_path` so it must be
87
+ # set before it.
88
+ :default_path, :only_failures,
89
+
90
+ # These must be set before `requires` to support checking
91
+ # `config.files_to_run` from within `spec_helper.rb` when a
92
+ # `-rspec_helper` option is used.
93
+ :files_or_directories_to_run, :pattern, :exclude_pattern,
94
+
95
+ # Necessary so that the `--seed` option is applied before requires,
96
+ # in case required files do something with the provided seed.
97
+ # (such as seed global randomization with it).
98
+ :order,
99
+
100
+ # In general, we want to require the specified files as early as
101
+ # possible. The `--require` option is specifically intended to allow
102
+ # early requires. For later requires, they can just put the require in
103
+ # their spec files, but `--require` provides a unique opportunity for
104
+ # users to instruct RSpec to load an extension file early for maximum
105
+ # flexibility.
106
+ :requires
107
+ ]
108
+
109
+ def process_options_into(config)
110
+ opts = options.reject { |k, _| UNPROCESSABLE_OPTIONS.include? k }
111
+
112
+ order(opts.keys).each do |key|
113
+ force?(key) ? config.force(key => opts[key]) : config.__send__("#{key}=", opts[key])
114
+ end
115
+ end
116
+
117
+ def load_formatters_into(config)
118
+ options[:formatters].each { |pair| config.add_formatter(*pair) } if options[:formatters]
119
+ end
120
+
121
+ def file_options
122
+ if custom_options_file
123
+ [custom_options]
124
+ else
125
+ [global_options, project_options, local_options]
126
+ end
127
+ end
128
+
129
+ def env_options
130
+ return {} unless ENV['SPEC_OPTS']
131
+
132
+ parse_args_ignoring_files_or_dirs_to_run(
133
+ Shellwords.split(ENV["SPEC_OPTS"]),
134
+ "ENV['SPEC_OPTS']"
135
+ )
136
+ end
137
+
138
+ def command_line_options
139
+ @command_line_options ||= Parser.parse(@args)
140
+ end
141
+
142
+ def custom_options
143
+ options_from(custom_options_file)
144
+ end
145
+
146
+ def local_options
147
+ @local_options ||= options_from(local_options_file)
148
+ end
149
+
150
+ def project_options
151
+ @project_options ||= options_from(project_options_file)
152
+ end
153
+
154
+ def global_options
155
+ @global_options ||= options_from(global_options_file)
156
+ end
157
+
158
+ def options_from(path)
159
+ args = args_from_options_file(path)
160
+ parse_args_ignoring_files_or_dirs_to_run(args, path)
161
+ end
162
+
163
+ def parse_args_ignoring_files_or_dirs_to_run(args, source)
164
+ options = Parser.parse(args, source)
165
+ options.delete(:files_or_directories_to_run)
166
+ options
167
+ end
168
+
169
+ def args_from_options_file(path)
170
+ return [] unless path && File.exist?(path)
171
+ config_string = options_file_as_erb_string(path)
172
+ FlatMap.flat_map(config_string.split(/\n+/), &:shellsplit)
173
+ end
174
+
175
+ def options_file_as_erb_string(path)
176
+ if RUBY_VERSION >= '2.6'
177
+ ERB.new(File.read(path), :trim_mode => '-').result(binding)
178
+ else
179
+ ERB.new(File.read(path), nil, '-').result(binding)
180
+ end
181
+ end
182
+
183
+ def custom_options_file
184
+ command_line_options[:custom_options_file]
185
+ end
186
+
187
+ def project_options_file
188
+ "./.rspec"
189
+ end
190
+
191
+ def local_options_file
192
+ "./.rspec-local"
193
+ end
194
+
195
+ def global_options_file
196
+ xdg_options_file_if_exists || home_options_file_path
197
+ end
198
+
199
+ def xdg_options_file_if_exists
200
+ path = xdg_options_file_path
201
+ if path && File.exist?(path)
202
+ path
203
+ end
204
+ end
205
+
206
+ def home_options_file_path
207
+ File.join(File.expand_path("~"), ".rspec")
208
+ rescue ArgumentError
209
+ # :nocov:
210
+ RSpec.warning "Unable to find ~/.rspec because the HOME environment variable is not set"
211
+ nil
212
+ # :nocov:
213
+ end
214
+
215
+ def xdg_options_file_path
216
+ xdg_config_home = resolve_xdg_config_home
217
+ if xdg_config_home
218
+ File.join(xdg_config_home, "rspec", "options")
219
+ end
220
+ end
221
+
222
+ def resolve_xdg_config_home
223
+ File.expand_path(ENV.fetch("XDG_CONFIG_HOME", "~/.config"))
224
+ rescue ArgumentError
225
+ # :nocov:
226
+ # On Ruby 2.4, `File.expand("~")` works even if `ENV['HOME']` is not set.
227
+ # But on earlier versions, it fails.
228
+ nil
229
+ # :nocov:
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,113 @@
1
+ require 'drb/drb'
2
+
3
+ module RSpec
4
+ module Core
5
+ # @private
6
+ class DRbRunner
7
+ def initialize(options, configuration=RSpec.configuration)
8
+ @options = options
9
+ @configuration = configuration
10
+ end
11
+
12
+ def drb_port
13
+ @options.options[:drb_port] || ENV['RSPEC_DRB'] || 8989
14
+ end
15
+
16
+ def run(err, out)
17
+ begin
18
+ DRb.start_service("druby://localhost:0")
19
+ rescue SocketError, Errno::EADDRNOTAVAIL
20
+ DRb.start_service("druby://:0")
21
+ end
22
+ spec_server = DRbObject.new_with_uri("druby://127.0.0.1:#{drb_port}")
23
+ spec_server.run(drb_argv, err, out)
24
+ end
25
+
26
+ def drb_argv
27
+ @drb_argv ||= begin
28
+ @options.configure_filter_manager(@configuration.filter_manager)
29
+ DRbOptions.new(@options.options, @configuration.filter_manager).options
30
+ end
31
+ end
32
+ end
33
+
34
+ # @private
35
+ class DRbOptions
36
+ def initialize(submitted_options, filter_manager)
37
+ @submitted_options = submitted_options
38
+ @filter_manager = filter_manager
39
+ end
40
+
41
+ def options
42
+ argv = []
43
+ argv << "--color" if @submitted_options[:color]
44
+ argv << "--force-color" if @submitted_options[:color_mode] == :on
45
+ argv << "--no-color" if @submitted_options[:color_mode] == :off
46
+ argv << "--profile" if @submitted_options[:profile_examples]
47
+ argv << "--backtrace" if @submitted_options[:full_backtrace]
48
+ argv << "--tty" if @submitted_options[:tty]
49
+ argv << "--fail-fast" if @submitted_options[:fail_fast]
50
+ argv << "--options" << @submitted_options[:custom_options_file] if @submitted_options[:custom_options_file]
51
+ argv << "--order" << @submitted_options[:order] if @submitted_options[:order]
52
+
53
+ add_failure_exit_code(argv)
54
+ add_full_description(argv)
55
+ add_filter(argv, :inclusion, @filter_manager.inclusions)
56
+ add_filter(argv, :exclusion, @filter_manager.exclusions)
57
+ add_formatters(argv)
58
+ add_libs(argv)
59
+ add_requires(argv)
60
+
61
+ argv + @submitted_options[:files_or_directories_to_run]
62
+ end
63
+
64
+ def add_failure_exit_code(argv)
65
+ return unless @submitted_options[:failure_exit_code]
66
+
67
+ argv << "--failure-exit-code" << @submitted_options[:failure_exit_code].to_s
68
+ end
69
+
70
+ def add_full_description(argv)
71
+ return unless @submitted_options[:full_description]
72
+
73
+ # The argument to --example is regexp-escaped before being stuffed
74
+ # into a regexp when received for the first time (see OptionParser).
75
+ # Hence, merely grabbing the source of this regexp will retain the
76
+ # backslashes, so we must remove them.
77
+ @submitted_options[:full_description].each do |description|
78
+ argv << "--example" << description.source.delete('\\')
79
+ end
80
+ end
81
+
82
+ CONDITIONAL_FILTERS = [:if, :unless]
83
+
84
+ def add_filter(argv, name, hash)
85
+ hash.each_pair do |k, v|
86
+ next if CONDITIONAL_FILTERS.include?(k)
87
+ tag = name == :inclusion ? k.to_s : "~#{k}".dup
88
+ tag << ":#{v}" if v.is_a?(String)
89
+ argv << "--tag" << tag
90
+ end unless hash.empty?
91
+ end
92
+
93
+ def add_formatters(argv)
94
+ @submitted_options[:formatters].each do |pair|
95
+ argv << "--format" << pair[0]
96
+ argv << "--out" << pair[1] if pair[1]
97
+ end if @submitted_options[:formatters]
98
+ end
99
+
100
+ def add_libs(argv)
101
+ @submitted_options[:libs].each do |path|
102
+ argv << "-I" << path
103
+ end if @submitted_options[:libs]
104
+ end
105
+
106
+ def add_requires(argv)
107
+ @submitted_options[:requires].each do |path|
108
+ argv << "--require" << path
109
+ end if @submitted_options[:requires]
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,98 @@
1
+ module RSpec
2
+ module Core
3
+ # DSL defines methods to group examples, most notably `describe`,
4
+ # and exposes them as class methods of {RSpec}. They can also be
5
+ # exposed globally (on `main` and instances of `Module`) through
6
+ # the {Configuration} option `expose_dsl_globally`.
7
+ #
8
+ # By default the methods `describe`, `context` and `example_group`
9
+ # are exposed. These methods define a named context for one or
10
+ # more examples. The given block is evaluated in the context of
11
+ # a generated subclass of {RSpec::Core::ExampleGroup}.
12
+ #
13
+ # ## Examples:
14
+ #
15
+ # RSpec.describe "something" do
16
+ # context "when something is a certain way" do
17
+ # it "does something" do
18
+ # # example code goes here
19
+ # end
20
+ # end
21
+ # end
22
+ #
23
+ # @see ExampleGroup
24
+ # @see ExampleGroup.example_group
25
+ module DSL
26
+ # @private
27
+ def self.example_group_aliases
28
+ @example_group_aliases ||= []
29
+ end
30
+
31
+ # @private
32
+ def self.exposed_globally?
33
+ @exposed_globally ||= false
34
+ end
35
+
36
+ # @private
37
+ def self.expose_example_group_alias(name)
38
+ return if example_group_aliases.include?(name)
39
+
40
+ example_group_aliases << name
41
+
42
+ (class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block|
43
+ group = RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block)
44
+ RSpec.world.record(group)
45
+ group
46
+ end
47
+
48
+ expose_example_group_alias_globally(name) if exposed_globally?
49
+ end
50
+
51
+ class << self
52
+ # @private
53
+ attr_accessor :top_level
54
+ end
55
+
56
+ # Adds the describe method to Module and the top level binding.
57
+ # @api private
58
+ def self.expose_globally!
59
+ return if exposed_globally?
60
+
61
+ example_group_aliases.each do |method_name|
62
+ expose_example_group_alias_globally(method_name)
63
+ end
64
+
65
+ @exposed_globally = true
66
+ end
67
+
68
+ # Removes the describe method from Module and the top level binding.
69
+ # @api private
70
+ def self.remove_globally!
71
+ return unless exposed_globally?
72
+
73
+ example_group_aliases.each do |method_name|
74
+ change_global_dsl { undef_method method_name }
75
+ end
76
+
77
+ @exposed_globally = false
78
+ end
79
+
80
+ # @private
81
+ def self.expose_example_group_alias_globally(method_name)
82
+ change_global_dsl do
83
+ remove_method(method_name) if method_defined?(method_name)
84
+ define_method(method_name) { |*a, &b| ::RSpec.__send__(method_name, *a, &b) }
85
+ end
86
+ end
87
+
88
+ # @private
89
+ def self.change_global_dsl(&changes)
90
+ (class << top_level; self; end).class_exec(&changes)
91
+ Module.class_exec(&changes)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # Capture main without an eval.
98
+ ::RSpec::Core::DSL.top_level = self
@@ -0,0 +1,656 @@
1
+ module RSpec
2
+ module Core
3
+ # Wrapper for an instance of a subclass of {ExampleGroup}. An instance of
4
+ # `RSpec::Core::Example` is returned by example definition methods
5
+ # such as {ExampleGroup.it it} and is yielded to the {ExampleGroup.it it},
6
+ # {Hooks#before before}, {Hooks#after after}, {Hooks#around around},
7
+ # {MemoizedHelpers::ClassMethods#let let} and
8
+ # {MemoizedHelpers::ClassMethods#subject subject} blocks.
9
+ #
10
+ # This allows us to provide rich metadata about each individual
11
+ # example without adding tons of methods directly to the ExampleGroup
12
+ # that users may inadvertently redefine.
13
+ #
14
+ # Useful for configuring logging and/or taking some action based
15
+ # on the state of an example's metadata.
16
+ #
17
+ # @example
18
+ #
19
+ # RSpec.configure do |config|
20
+ # config.before do |example|
21
+ # log example.description
22
+ # end
23
+ #
24
+ # config.after do |example|
25
+ # log example.description
26
+ # end
27
+ #
28
+ # config.around do |example|
29
+ # log example.description
30
+ # example.run
31
+ # end
32
+ # end
33
+ #
34
+ # shared_examples "auditable" do
35
+ # it "does something" do
36
+ # log "#{example.full_description}: #{auditable.inspect}"
37
+ # auditable.should do_something
38
+ # end
39
+ # end
40
+ #
41
+ # @see ExampleGroup
42
+ # @note Example blocks are evaluated in the context of an instance
43
+ # of an `ExampleGroup`, not in the context of an instance of `Example`.
44
+ class Example
45
+ # @private
46
+ #
47
+ # Used to define methods that delegate to this example's metadata.
48
+ def self.delegate_to_metadata(key)
49
+ define_method(key) { @metadata[key] }
50
+ end
51
+
52
+ # @return [ExecutionResult] represents the result of running this example.
53
+ delegate_to_metadata :execution_result
54
+ # @return [String] the relative path to the file where this example was
55
+ # defined.
56
+ delegate_to_metadata :file_path
57
+ # @return [String] the full description (including the docstrings of
58
+ # all parent example groups).
59
+ delegate_to_metadata :full_description
60
+ # @return [String] the exact source location of this example in a form
61
+ # like `./path/to/spec.rb:17`
62
+ delegate_to_metadata :location
63
+ # @return [Boolean] flag that indicates that the example is not expected
64
+ # to pass. It will be run and will either have a pending result (if a
65
+ # failure occurs) or a failed result (if no failure occurs).
66
+ delegate_to_metadata :pending
67
+ # @return [Boolean] flag that will cause the example to not run.
68
+ # The {ExecutionResult} status will be `:pending`.
69
+ delegate_to_metadata :skip
70
+
71
+ # Returns the string submitted to `example` or its aliases (e.g.
72
+ # `specify`, `it`, etc). If no string is submitted (e.g.
73
+ # `it { is_expected.to do_something }`) it returns the message generated
74
+ # by the matcher if there is one, otherwise returns a message including
75
+ # the location of the example.
76
+ def description
77
+ description = if metadata[:description].to_s.empty?
78
+ location_description
79
+ else
80
+ metadata[:description]
81
+ end
82
+
83
+ RSpec.configuration.format_docstrings_block.call(description)
84
+ end
85
+
86
+ # Returns a description of the example that always includes the location.
87
+ def inspect_output
88
+ inspect_output = "\"#{description}\""
89
+ unless metadata[:description].to_s.empty?
90
+ inspect_output += " (#{location})"
91
+ end
92
+ inspect_output
93
+ end
94
+
95
+ # Returns the location-based argument that can be passed to the `rspec` command to rerun this example.
96
+ def location_rerun_argument
97
+ @location_rerun_argument ||= begin
98
+ loaded_spec_files = RSpec.configuration.loaded_spec_files
99
+
100
+ Metadata.ascending(metadata) do |meta|
101
+ return meta[:location] if loaded_spec_files.include?(meta[:absolute_file_path])
102
+ end
103
+ end
104
+ end
105
+
106
+ # Returns the location-based argument that can be passed to the `rspec` command to rerun this example.
107
+ #
108
+ # @deprecated Use {#location_rerun_argument} instead.
109
+ # @note If there are multiple examples identified by this location, they will use {#id}
110
+ # to rerun instead, but this method will still return the location (that's why it is deprecated!).
111
+ def rerun_argument
112
+ location_rerun_argument
113
+ end
114
+
115
+ # @return [String] the unique id of this example. Pass
116
+ # this at the command line to re-run this exact example.
117
+ def id
118
+ @id ||= Metadata.id_from(metadata)
119
+ end
120
+
121
+ # @private
122
+ def self.parse_id(id)
123
+ # http://rubular.com/r/OMZSAPcAfn
124
+ id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures
125
+ end
126
+
127
+ # Duplicates the example and overrides metadata with the provided
128
+ # hash.
129
+ #
130
+ # @param metadata_overrides [Hash] the hash to override the example metadata
131
+ # @return [Example] a duplicate of the example with modified metadata
132
+ def duplicate_with(metadata_overrides={})
133
+ new_metadata = metadata.clone.merge(metadata_overrides)
134
+
135
+ RSpec::Core::Metadata::RESERVED_KEYS.each do |reserved_key|
136
+ new_metadata.delete reserved_key
137
+ end
138
+
139
+ # don't clone the example group because the new example
140
+ # must belong to the same example group (not a clone).
141
+ #
142
+ # block is nil in new_metadata so we have to get it from metadata.
143
+ Example.new(example_group, description.clone,
144
+ new_metadata, metadata[:block])
145
+ end
146
+
147
+ # @private
148
+ def update_inherited_metadata(updates)
149
+ metadata.update(updates) do |_key, existing_example_value, _new_inherited_value|
150
+ existing_example_value
151
+ end
152
+ end
153
+
154
+ # @attr_reader
155
+ #
156
+ # Returns the first exception raised in the context of running this
157
+ # example (nil if no exception is raised).
158
+ attr_reader :exception
159
+
160
+ # @attr_reader
161
+ #
162
+ # Returns the metadata object associated with this example.
163
+ attr_reader :metadata
164
+
165
+ # @attr_reader
166
+ # @private
167
+ #
168
+ # Returns the example_group_instance that provides the context for
169
+ # running this example.
170
+ attr_reader :example_group_instance
171
+
172
+ # @attr
173
+ # @private
174
+ attr_accessor :clock
175
+
176
+ # Creates a new instance of Example.
177
+ # @param example_group_class [Class] the subclass of ExampleGroup in which
178
+ # this Example is declared
179
+ # @param description [String] the String passed to the `it` method (or
180
+ # alias)
181
+ # @param user_metadata [Hash] additional args passed to `it` to be used as
182
+ # metadata
183
+ # @param example_block [Proc] the block of code that represents the
184
+ # example
185
+ # @api private
186
+ def initialize(example_group_class, description, user_metadata, example_block=nil)
187
+ @example_group_class = example_group_class
188
+ @example_block = example_block
189
+
190
+ # Register the example with the group before creating the metadata hash.
191
+ # This is necessary since creating the metadata hash triggers
192
+ # `when_first_matching_example_defined` callbacks, in which users can
193
+ # load RSpec support code which defines hooks. For that to work, the
194
+ # examples and example groups must be registered at the time the
195
+ # support code is called or be defined afterwards.
196
+ # Begin defined beforehand but registered afterwards causes hooks to
197
+ # not be applied where they should.
198
+ example_group_class.examples << self
199
+
200
+ @metadata = Metadata::ExampleHash.create(
201
+ @example_group_class.metadata, user_metadata,
202
+ example_group_class.method(:next_runnable_index_for),
203
+ description, example_block
204
+ )
205
+
206
+ config = RSpec.configuration
207
+ config.apply_derived_metadata_to(@metadata)
208
+
209
+ # This should perhaps be done in `Metadata::ExampleHash.create`,
210
+ # but the logic there has no knowledge of `RSpec.world` and we
211
+ # want to keep it that way. It's easier to just assign it here.
212
+ @metadata[:last_run_status] = config.last_run_statuses[id]
213
+
214
+ @example_group_instance = @exception = nil
215
+ @clock = RSpec::Core::Time
216
+ @reporter = RSpec::Core::NullReporter
217
+ end
218
+
219
+ # Provide a human-readable representation of this class
220
+ def inspect
221
+ "#<#{self.class.name} #{description.inspect}>"
222
+ end
223
+ alias to_s inspect
224
+
225
+ # @return [RSpec::Core::Reporter] the current reporter for the example
226
+ attr_reader :reporter
227
+
228
+ # Returns the example group class that provides the context for running
229
+ # this example.
230
+ def example_group
231
+ @example_group_class
232
+ end
233
+
234
+ alias_method :pending?, :pending
235
+ alias_method :skipped?, :skip
236
+
237
+ # @api private
238
+ # instance_execs the block passed to the constructor in the context of
239
+ # the instance of {ExampleGroup}.
240
+ # @param example_group_instance the instance of an ExampleGroup subclass
241
+ def run(example_group_instance, reporter)
242
+ @example_group_instance = example_group_instance
243
+ @reporter = reporter
244
+ RSpec.configuration.configure_example(self, hooks)
245
+ RSpec.current_example = self
246
+
247
+ start(reporter)
248
+ Pending.mark_pending!(self, pending) if pending?
249
+
250
+ begin
251
+ if skipped?
252
+ Pending.mark_pending! self, skip
253
+ elsif !RSpec.configuration.dry_run?
254
+ with_around_and_singleton_context_hooks do
255
+ begin
256
+ run_before_example
257
+ @example_group_instance.instance_exec(self, &@example_block)
258
+
259
+ if pending?
260
+ Pending.mark_fixed! self
261
+
262
+ raise Pending::PendingExampleFixedError,
263
+ 'Expected example to fail since it is pending, but it passed.',
264
+ [location]
265
+ end
266
+ rescue Pending::SkipDeclaredInExample => _
267
+ # The "=> _" is normally useless but on JRuby it is a workaround
268
+ # for a bug that prevents us from getting backtraces:
269
+ # https://github.com/jruby/jruby/issues/4467
270
+ #
271
+ # no-op, required metadata has already been set by the `skip`
272
+ # method.
273
+ rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
274
+ set_exception(e)
275
+ ensure
276
+ run_after_example
277
+ end
278
+ end
279
+ end
280
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
281
+ set_exception(e)
282
+ ensure
283
+ @example_group_instance = nil # if you love something... let it go
284
+ end
285
+
286
+ finish(reporter)
287
+ ensure
288
+ execution_result.ensure_timing_set(clock)
289
+ RSpec.current_example = nil
290
+ end
291
+
292
+ if RSpec::Support::Ruby.jruby? || RUBY_VERSION.to_f < 1.9
293
+ # :nocov:
294
+ # For some reason, rescuing `Support::AllExceptionsExceptOnesWeMustNotRescue`
295
+ # in place of `Exception` above can cause the exit status to be the wrong
296
+ # thing. I have no idea why. See:
297
+ # https://github.com/rspec/rspec-core/pull/2063#discussion_r38284978
298
+ # @private
299
+ AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Exception
300
+ # :nocov:
301
+ else
302
+ # @private
303
+ AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Support::AllExceptionsExceptOnesWeMustNotRescue
304
+ end
305
+
306
+ # Wraps both a `Proc` and an {Example} for use in {Hooks#around
307
+ # around} hooks. In around hooks we need to yield this special
308
+ # kind of object (rather than the raw {Example}) because when
309
+ # there are multiple `around` hooks we have to wrap them recursively.
310
+ #
311
+ # @example
312
+ #
313
+ # RSpec.configure do |c|
314
+ # c.around do |ex| # Procsy which wraps the example
315
+ # if ex.metadata[:key] == :some_value && some_global_condition
316
+ # raise "some message"
317
+ # end
318
+ # ex.run # run delegates to ex.call.
319
+ # end
320
+ # end
321
+ #
322
+ # @note This class also exposes the instance methods of {Example},
323
+ # proxying them through to the wrapped {Example} instance.
324
+ class Procsy
325
+ # The {Example} instance.
326
+ attr_reader :example
327
+
328
+ Example.public_instance_methods(false).each do |name|
329
+ name_sym = name.to_sym
330
+ next if name_sym == :run || name_sym == :inspect || name_sym == :to_s
331
+
332
+ define_method(name) { |*a, &b| @example.__send__(name, *a, &b) }
333
+ end
334
+
335
+ Proc.public_instance_methods(false).each do |name|
336
+ name_sym = name.to_sym
337
+ next if name_sym == :call || name_sym == :inspect || name_sym == :to_s || name_sym == :to_proc
338
+
339
+ define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) }
340
+ end
341
+
342
+ # Calls the proc and notes that the example has been executed.
343
+ def call(*args, &block)
344
+ @executed = true
345
+ @proc.call(*args, &block)
346
+ end
347
+ alias run call
348
+
349
+ # Provides a wrapped proc that will update our `executed?` state when
350
+ # executed.
351
+ def to_proc
352
+ method(:call).to_proc
353
+ end
354
+
355
+ def initialize(example, &block)
356
+ @example = example
357
+ @proc = block
358
+ @executed = false
359
+ end
360
+
361
+ # @private
362
+ def wrap(&block)
363
+ self.class.new(example, &block)
364
+ end
365
+
366
+ # Indicates whether or not the around hook has executed the example.
367
+ def executed?
368
+ @executed
369
+ end
370
+
371
+ # @private
372
+ def inspect
373
+ @example.inspect.gsub('Example', 'ExampleProcsy')
374
+ end
375
+ end
376
+
377
+ # @private
378
+ #
379
+ # The exception that will be displayed to the user -- either the failure of
380
+ # the example or the `pending_exception` if the example is pending.
381
+ def display_exception
382
+ @exception || execution_result.pending_exception
383
+ end
384
+
385
+ # @private
386
+ #
387
+ # Assigns the exception that will be displayed to the user -- either the failure of
388
+ # the example or the `pending_exception` if the example is pending.
389
+ def display_exception=(ex)
390
+ if pending? && !(Pending::PendingExampleFixedError === ex)
391
+ @exception = nil
392
+ execution_result.pending_fixed = false
393
+ execution_result.pending_exception = ex
394
+ else
395
+ @exception = ex
396
+ end
397
+ end
398
+
399
+ # rubocop:disable Naming/AccessorMethodName
400
+
401
+ # @private
402
+ #
403
+ # Used internally to set an exception in an after hook, which
404
+ # captures the exception but doesn't raise it.
405
+ def set_exception(exception)
406
+ return self.display_exception = exception unless display_exception
407
+
408
+ unless RSpec::Core::MultipleExceptionError === display_exception
409
+ self.display_exception = RSpec::Core::MultipleExceptionError.new(display_exception)
410
+ end
411
+
412
+ display_exception.add exception
413
+ end
414
+
415
+ # @private
416
+ #
417
+ # Used to set the exception when `aggregate_failures` fails.
418
+ def set_aggregate_failures_exception(exception)
419
+ return set_exception(exception) unless display_exception
420
+
421
+ exception = RSpec::Core::MultipleExceptionError::InterfaceTag.for(exception)
422
+ exception.add display_exception
423
+ self.display_exception = exception
424
+ end
425
+
426
+ # rubocop:enable Naming/AccessorMethodName
427
+
428
+ # @private
429
+ #
430
+ # Used internally to set an exception and fail without actually executing
431
+ # the example when an exception is raised in before(:context).
432
+ def fail_with_exception(reporter, exception)
433
+ start(reporter)
434
+ set_exception(exception)
435
+ finish(reporter)
436
+ end
437
+
438
+ # @private
439
+ #
440
+ # Used internally to skip without actually executing the example when
441
+ # skip is used in before(:context).
442
+ def skip_with_exception(reporter, exception)
443
+ start(reporter)
444
+ Pending.mark_skipped! self, exception.argument
445
+ finish(reporter)
446
+ end
447
+
448
+ # @private
449
+ def instance_exec(*args, &block)
450
+ @example_group_instance.instance_exec(*args, &block)
451
+ end
452
+
453
+ private
454
+
455
+ def hooks
456
+ example_group_instance.singleton_class.hooks
457
+ end
458
+
459
+ def with_around_example_hooks
460
+ hooks.run(:around, :example, self) { yield }
461
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
462
+ set_exception(e)
463
+ end
464
+
465
+ def start(reporter)
466
+ reporter.example_started(self)
467
+ execution_result.started_at = clock.now
468
+ end
469
+
470
+ def finish(reporter)
471
+ pending_message = execution_result.pending_message
472
+
473
+ if @exception
474
+ execution_result.exception = @exception
475
+ record_finished :failed, reporter
476
+ reporter.example_failed self
477
+ false
478
+ elsif pending_message
479
+ execution_result.pending_message = pending_message
480
+ record_finished :pending, reporter
481
+ reporter.example_pending self
482
+ true
483
+ else
484
+ record_finished :passed, reporter
485
+ reporter.example_passed self
486
+ true
487
+ end
488
+ end
489
+
490
+ def record_finished(status, reporter)
491
+ execution_result.record_finished(status, clock.now)
492
+ reporter.example_finished(self)
493
+ end
494
+
495
+ def run_before_example
496
+ @example_group_instance.setup_mocks_for_rspec
497
+ hooks.run(:before, :example, self)
498
+ end
499
+
500
+ def with_around_and_singleton_context_hooks
501
+ singleton_context_hooks_host = example_group_instance.singleton_class
502
+ singleton_context_hooks_host.run_before_context_hooks(example_group_instance)
503
+ with_around_example_hooks { yield }
504
+ ensure
505
+ singleton_context_hooks_host.run_after_context_hooks(example_group_instance)
506
+ end
507
+
508
+ def run_after_example
509
+ assign_generated_description if defined?(::RSpec::Matchers)
510
+ hooks.run(:after, :example, self)
511
+ verify_mocks
512
+ ensure
513
+ @example_group_instance.teardown_mocks_for_rspec
514
+ end
515
+
516
+ def verify_mocks
517
+ @example_group_instance.verify_mocks_for_rspec if mocks_need_verification?
518
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
519
+ set_exception(e)
520
+ end
521
+
522
+ def mocks_need_verification?
523
+ exception.nil? || execution_result.pending_fixed?
524
+ end
525
+
526
+ def assign_generated_description
527
+ if metadata[:description].empty? && (description = generate_description)
528
+ metadata[:description] = description
529
+ metadata[:full_description] += description
530
+ end
531
+ ensure
532
+ RSpec::Matchers.clear_generated_description
533
+ end
534
+
535
+ def generate_description
536
+ RSpec::Matchers.generated_description
537
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
538
+ location_description + " (Got an error when generating description " \
539
+ "from matcher: #{e.class}: #{e.message} -- #{e.backtrace.first})"
540
+ end
541
+
542
+ def location_description
543
+ "example at #{location}"
544
+ end
545
+
546
+ # Represents the result of executing an example.
547
+ # Behaves like a hash for backwards compatibility.
548
+ class ExecutionResult
549
+ include HashImitatable
550
+
551
+ # @return [Symbol] `:passed`, `:failed` or `:pending`.
552
+ attr_accessor :status
553
+
554
+ # @return [Exception, nil] The failure, if there was one.
555
+ attr_accessor :exception
556
+
557
+ # @return [Time] When the example started.
558
+ attr_accessor :started_at
559
+
560
+ # @return [Time] When the example finished.
561
+ attr_accessor :finished_at
562
+
563
+ # @return [Float] How long the example took in seconds.
564
+ attr_accessor :run_time
565
+
566
+ # @return [String, nil] The reason the example was pending,
567
+ # or nil if the example was not pending.
568
+ attr_accessor :pending_message
569
+
570
+ # @return [Exception, nil] The exception triggered while
571
+ # executing the pending example. If no exception was triggered
572
+ # it would no longer get a status of `:pending` unless it was
573
+ # tagged with `:skip`.
574
+ attr_accessor :pending_exception
575
+
576
+ # @return [Boolean] For examples tagged with `:pending`,
577
+ # this indicates whether or not it now passes.
578
+ attr_accessor :pending_fixed
579
+
580
+ alias pending_fixed? pending_fixed
581
+
582
+ # @return [Boolean] Indicates if the example was completely skipped
583
+ # (typically done via `:skip` metadata or the `skip` method). Skipped examples
584
+ # will have a `:pending` result. A `:pending` result can also come from examples
585
+ # that were marked as `:pending`, which causes them to be run, and produces a
586
+ # `:failed` result if the example passes.
587
+ def example_skipped?
588
+ status == :pending && !pending_exception
589
+ end
590
+
591
+ # @api private
592
+ # Records the finished status of the example.
593
+ def record_finished(status, finished_at)
594
+ self.status = status
595
+ calculate_run_time(finished_at)
596
+ end
597
+
598
+ # @api private
599
+ # Populates finished_at and run_time if it has not yet been set
600
+ def ensure_timing_set(clock)
601
+ calculate_run_time(clock.now) unless finished_at
602
+ end
603
+
604
+ private
605
+
606
+ def calculate_run_time(finished_at)
607
+ self.finished_at = finished_at
608
+ self.run_time = (finished_at - started_at).to_f
609
+ end
610
+
611
+ # For backwards compatibility we present `status` as a string
612
+ # when presenting the legacy hash interface.
613
+ def hash_for_delegation
614
+ super.tap do |hash|
615
+ hash[:status] &&= status.to_s
616
+ end
617
+ end
618
+
619
+ def set_value(name, value)
620
+ value &&= value.to_sym if name == :status
621
+ super(name, value)
622
+ end
623
+
624
+ def get_value(name)
625
+ if name == :status
626
+ status.to_s if status
627
+ else
628
+ super
629
+ end
630
+ end
631
+
632
+ def issue_deprecation(_method_name, *_args)
633
+ RSpec.deprecate("Treating `metadata[:execution_result]` as a hash",
634
+ :replacement => "the attributes methods to access the data")
635
+ end
636
+ end
637
+ end
638
+
639
+ # @private
640
+ # Provides an execution context for before/after :suite hooks.
641
+ class SuiteHookContext < Example
642
+ def initialize(hook_description, reporter)
643
+ super(AnonymousExampleGroup, hook_description, {})
644
+ @example_group_instance = AnonymousExampleGroup.new
645
+ @reporter = reporter
646
+ end
647
+
648
+ # rubocop:disable Naming/AccessorMethodName
649
+ def set_exception(exception)
650
+ reporter.notify_non_example_exception(exception, "An error occurred in #{description}.")
651
+ RSpec.world.wants_to_quit = true
652
+ end
653
+ # rubocop:enable Naming/AccessorMethodName
654
+ end
655
+ end
656
+ end