rspec-core 3.0.0.beta2 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +1 -0
  5. data/Changelog.md +297 -57
  6. data/README.md +16 -13
  7. data/lib/rspec/core.rb +55 -84
  8. data/lib/rspec/core/backport_random.rb +35 -3
  9. data/lib/rspec/core/backtrace_formatter.rb +4 -13
  10. data/lib/rspec/core/configuration.rb +330 -114
  11. data/lib/rspec/core/configuration_options.rb +38 -22
  12. data/lib/rspec/core/drb.rb +111 -0
  13. data/lib/rspec/core/dsl.rb +8 -2
  14. data/lib/rspec/core/example.rb +203 -94
  15. data/lib/rspec/core/example_group.rb +344 -316
  16. data/lib/rspec/core/filter_manager.rb +135 -90
  17. data/lib/rspec/core/flat_map.rb +1 -0
  18. data/lib/rspec/core/formatters.rb +50 -14
  19. data/lib/rspec/core/formatters/base_formatter.rb +32 -138
  20. data/lib/rspec/core/formatters/base_text_formatter.rb +32 -253
  21. data/lib/rspec/core/formatters/console_codes.rb +65 -0
  22. data/lib/rspec/core/formatters/deprecation_formatter.rb +24 -15
  23. data/lib/rspec/core/formatters/documentation_formatter.rb +7 -10
  24. data/lib/rspec/core/formatters/helpers.rb +15 -9
  25. data/lib/rspec/core/formatters/html_formatter.rb +17 -16
  26. data/lib/rspec/core/formatters/html_printer.rb +1 -0
  27. data/lib/rspec/core/formatters/json_formatter.rb +18 -20
  28. data/lib/rspec/core/formatters/profile_formatter.rb +67 -0
  29. data/lib/rspec/core/formatters/progress_formatter.rb +6 -7
  30. data/lib/rspec/core/formatters/snippet_extractor.rb +8 -6
  31. data/lib/rspec/core/hooks.rb +131 -125
  32. data/lib/rspec/core/memoized_helpers.rb +31 -26
  33. data/lib/rspec/core/metadata.rb +277 -184
  34. data/lib/rspec/core/metadata_filter.rb +86 -0
  35. data/lib/rspec/core/minitest_assertions_adapter.rb +28 -0
  36. data/lib/rspec/core/mocking_adapters/flexmock.rb +1 -1
  37. data/lib/rspec/core/mocking_adapters/mocha.rb +1 -1
  38. data/lib/rspec/core/mocking_adapters/null.rb +1 -1
  39. data/lib/rspec/core/mocking_adapters/rr.rb +2 -1
  40. data/lib/rspec/core/mocking_adapters/rspec.rb +1 -1
  41. data/lib/rspec/core/notifications.rb +435 -24
  42. data/lib/rspec/core/option_parser.rb +16 -25
  43. data/lib/rspec/core/ordering.rb +3 -1
  44. data/lib/rspec/core/pending.rb +57 -33
  45. data/lib/rspec/core/project_initializer.rb +2 -0
  46. data/lib/rspec/core/project_initializer/spec_helper.rb +5 -4
  47. data/lib/rspec/core/rake_task.rb +45 -20
  48. data/lib/rspec/core/reporter.rb +50 -22
  49. data/lib/rspec/core/ruby_project.rb +1 -0
  50. data/lib/rspec/core/runner.rb +93 -39
  51. data/lib/rspec/core/shared_context.rb +7 -5
  52. data/lib/rspec/core/shared_example_group.rb +85 -77
  53. data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
  54. data/lib/rspec/core/version.rb +3 -1
  55. data/lib/rspec/core/warnings.rb +35 -17
  56. data/lib/rspec/core/world.rb +57 -5
  57. metadata +56 -369
  58. metadata.gz.sig +3 -3
  59. data/features/README.md +0 -13
  60. data/features/Upgrade.md +0 -352
  61. data/features/command_line/README.md +0 -25
  62. data/features/command_line/dry_run.feature +0 -29
  63. data/features/command_line/example_name_option.feature +0 -97
  64. data/features/command_line/exit_status.feature +0 -82
  65. data/features/command_line/fail_fast.feature +0 -26
  66. data/features/command_line/format_option.feature +0 -75
  67. data/features/command_line/init.feature +0 -57
  68. data/features/command_line/line_number_appended_to_path.feature +0 -140
  69. data/features/command_line/line_number_option.feature +0 -58
  70. data/features/command_line/order.feature +0 -25
  71. data/features/command_line/pattern_option.feature +0 -49
  72. data/features/command_line/rake_task.feature +0 -122
  73. data/features/command_line/randomization.feature +0 -63
  74. data/features/command_line/require_option.feature +0 -43
  75. data/features/command_line/ruby.feature +0 -23
  76. data/features/command_line/tag.feature +0 -98
  77. data/features/command_line/warnings_option.feature +0 -29
  78. data/features/configuration/alias_example_to.feature +0 -39
  79. data/features/configuration/backtrace_exclusion_patterns.feature +0 -105
  80. data/features/configuration/custom_settings.feature +0 -84
  81. data/features/configuration/default_path.feature +0 -38
  82. data/features/configuration/deprecation_stream.feature +0 -58
  83. data/features/configuration/enable_global_dsl.feature +0 -54
  84. data/features/configuration/fail_fast.feature +0 -77
  85. data/features/configuration/failure_exit_code.feature +0 -36
  86. data/features/configuration/order_and_seed.feature +0 -3
  87. data/features/configuration/output_stream.feature +0 -24
  88. data/features/configuration/overriding_global_ordering.feature +0 -93
  89. data/features/configuration/pattern.feature +0 -38
  90. data/features/configuration/profile.feature +0 -220
  91. data/features/configuration/read_options_from_file.feature +0 -90
  92. data/features/configuration/run_all_when_everything_filtered.feature +0 -76
  93. data/features/example_groups/aliasing.feature +0 -48
  94. data/features/example_groups/basic_structure.feature +0 -55
  95. data/features/example_groups/shared_context.feature +0 -74
  96. data/features/example_groups/shared_examples.feature +0 -286
  97. data/features/expectation_framework_integration/configure_expectation_framework.feature +0 -102
  98. data/features/filtering/exclusion_filters.feature +0 -135
  99. data/features/filtering/if_and_unless.feature +0 -138
  100. data/features/filtering/inclusion_filters.feature +0 -101
  101. data/features/formatters/configurable_colors.feature +0 -31
  102. data/features/formatters/custom_formatter.feature +0 -68
  103. data/features/formatters/json_formatter.feature +0 -30
  104. data/features/formatters/regression_tests.feature +0 -95
  105. data/features/formatters/text_formatter.feature +0 -46
  106. data/features/helper_methods/arbitrary_methods.feature +0 -40
  107. data/features/helper_methods/let.feature +0 -50
  108. data/features/helper_methods/modules.feature +0 -146
  109. data/features/hooks/around_hooks.feature +0 -344
  110. data/features/hooks/before_and_after_hooks.feature +0 -427
  111. data/features/hooks/filtering.feature +0 -232
  112. data/features/metadata/current_example.feature +0 -56
  113. data/features/metadata/described_class.feature +0 -17
  114. data/features/metadata/user_defined.feature +0 -100
  115. data/features/mock_framework_integration/use_any_framework.feature +0 -106
  116. data/features/mock_framework_integration/use_flexmock.feature +0 -94
  117. data/features/mock_framework_integration/use_mocha.feature +0 -95
  118. data/features/mock_framework_integration/use_rr.feature +0 -96
  119. data/features/mock_framework_integration/use_rspec.feature +0 -95
  120. data/features/pending_and_skipped_examples/README.md +0 -3
  121. data/features/pending_and_skipped_examples/pending_examples.feature +0 -118
  122. data/features/pending_and_skipped_examples/skipped_examples.feature +0 -106
  123. data/features/spec_files/arbitrary_file_suffix.feature +0 -13
  124. data/features/step_definitions/additional_cli_steps.rb +0 -83
  125. data/features/subject/explicit_subject.feature +0 -101
  126. data/features/subject/implicit_subject.feature +0 -63
  127. data/features/subject/one_liner_syntax.feature +0 -71
  128. data/features/support/env.rb +0 -21
  129. data/features/support/require_expect_syntax_in_aruba_specs.rb +0 -16
  130. data/features/support/rubinius.rb +0 -6
  131. data/lib/rspec/core/command_line.rb +0 -35
  132. data/lib/rspec/core/drb_command_line.rb +0 -26
  133. data/lib/rspec/core/drb_options.rb +0 -87
  134. data/lib/rspec/core/formatters/legacy_formatter.rb +0 -227
  135. data/lib/rspec/core/shared_example_group/collection.rb +0 -27
  136. data/spec/command_line/order_spec.rb +0 -211
  137. data/spec/rspec/core/backtrace_formatter_spec.rb +0 -230
  138. data/spec/rspec/core/command_line_spec.rb +0 -112
  139. data/spec/rspec/core/command_line_spec_output.txt +0 -0
  140. data/spec/rspec/core/configuration_options_spec.rb +0 -409
  141. data/spec/rspec/core/configuration_spec.rb +0 -1479
  142. data/spec/rspec/core/drb_command_line_spec.rb +0 -102
  143. data/spec/rspec/core/drb_options_spec.rb +0 -193
  144. data/spec/rspec/core/dsl_spec.rb +0 -88
  145. data/spec/rspec/core/example_group_spec.rb +0 -1533
  146. data/spec/rspec/core/example_spec.rb +0 -642
  147. data/spec/rspec/core/filter_manager_spec.rb +0 -229
  148. data/spec/rspec/core/formatters/base_formatter_spec.rb +0 -64
  149. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +0 -303
  150. data/spec/rspec/core/formatters/deprecation_formatter_spec.rb +0 -208
  151. data/spec/rspec/core/formatters/documentation_formatter_spec.rb +0 -75
  152. data/spec/rspec/core/formatters/helpers_spec.rb +0 -104
  153. data/spec/rspec/core/formatters/html_formatted-2.1.0.html +0 -392
  154. data/spec/rspec/core/formatters/html_formatted.html +0 -397
  155. data/spec/rspec/core/formatters/html_formatter_spec.rb +0 -122
  156. data/spec/rspec/core/formatters/json_formatter_spec.rb +0 -206
  157. data/spec/rspec/core/formatters/legacy_formatter_spec.rb +0 -137
  158. data/spec/rspec/core/formatters/progress_formatter_spec.rb +0 -43
  159. data/spec/rspec/core/formatters/snippet_extractor_spec.rb +0 -26
  160. data/spec/rspec/core/formatters_spec.rb +0 -120
  161. data/spec/rspec/core/hooks_filtering_spec.rb +0 -227
  162. data/spec/rspec/core/hooks_spec.rb +0 -294
  163. data/spec/rspec/core/memoized_helpers_spec.rb +0 -495
  164. data/spec/rspec/core/metadata_spec.rb +0 -491
  165. data/spec/rspec/core/option_parser_spec.rb +0 -262
  166. data/spec/rspec/core/ordering_spec.rb +0 -102
  167. data/spec/rspec/core/pending_example_spec.rb +0 -117
  168. data/spec/rspec/core/pending_spec.rb +0 -8
  169. data/spec/rspec/core/project_initializer_spec.rb +0 -73
  170. data/spec/rspec/core/rake_task_spec.rb +0 -146
  171. data/spec/rspec/core/random_spec.rb +0 -47
  172. data/spec/rspec/core/reporter_spec.rb +0 -155
  173. data/spec/rspec/core/resources/a_bar.rb +0 -0
  174. data/spec/rspec/core/resources/a_foo.rb +0 -0
  175. data/spec/rspec/core/resources/a_spec.rb +0 -1
  176. data/spec/rspec/core/resources/custom_example_group_runner.rb +0 -14
  177. data/spec/rspec/core/resources/formatter_specs.rb +0 -58
  178. data/spec/rspec/core/resources/utf8_encoded.rb +0 -8
  179. data/spec/rspec/core/rspec_matchers_spec.rb +0 -45
  180. data/spec/rspec/core/ruby_project_spec.rb +0 -26
  181. data/spec/rspec/core/runner_spec.rb +0 -151
  182. data/spec/rspec/core/shared_context_spec.rb +0 -102
  183. data/spec/rspec/core/shared_example_group/collection_spec.rb +0 -57
  184. data/spec/rspec/core/shared_example_group_spec.rb +0 -114
  185. data/spec/rspec/core/warnings_spec.rb +0 -29
  186. data/spec/rspec/core/world_spec.rb +0 -142
  187. data/spec/rspec/core_spec.rb +0 -91
  188. data/spec/spec_helper.rb +0 -160
  189. data/spec/support/config_options_helper.rb +0 -13
  190. data/spec/support/formatter_support.rb +0 -83
  191. data/spec/support/helper_methods.rb +0 -26
  192. data/spec/support/isolate_load_path_mutation.rb +0 -5
  193. data/spec/support/isolated_directory.rb +0 -10
  194. data/spec/support/isolated_home_directory.rb +0 -16
  195. data/spec/support/legacy_formatter_using_sub_classing_example.rb +0 -87
  196. data/spec/support/matchers.rb +0 -85
  197. data/spec/support/mathn_integration_support.rb +0 -12
  198. data/spec/support/old_style_formatter_example.rb +0 -69
  199. data/spec/support/shared_example_groups.rb +0 -13
  200. data/spec/support/spec_files.rb +0 -44
  201. data/spec/support/stderr_splitter.rb +0 -36
@@ -1,47 +1,59 @@
1
1
  require 'erb'
2
2
  require 'shellwords'
3
+ require 'set'
3
4
 
4
5
  module RSpec
5
6
  module Core
6
- # @private
7
+ # Responsible for utilizing externally provided configuration options,
8
+ # whether via the command line, `.rspec`, `~/.rspec`, `.rspec-local`
9
+ # or a custom options file.
7
10
  class ConfigurationOptions
11
+ # @param args [Array<String>] command line arguments
8
12
  def initialize(args)
9
13
  @args = args.dup
14
+ organize_options
10
15
  end
11
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
12
21
  def configure(config)
13
- config.filter_manager = filter_manager
14
-
15
22
  process_options_into config
23
+ configure_filter_manager config.filter_manager
16
24
  load_formatters_into config
17
25
  end
18
26
 
19
- def options
20
- @options ||= (file_options << command_line_options << env_options).
21
- each {|opts|
22
- filter_manager.include opts.delete(:inclusion_filter) if opts.has_key?(:inclusion_filter)
23
- filter_manager.exclude opts.delete(:exclusion_filter) if opts.has_key?(:exclusion_filter)
24
- }.
25
- inject(:libs => [], :requires => []) {|h, opts|
26
- h.merge(opts) {|k, oldval, newval|
27
- [:libs, :requires].include?(k) ? oldval + newval : newval
28
- }
29
- }
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_inclusions.each { |val| filter_manager.include(val) }
32
+ @filter_manager_exclusions.each { |val| filter_manager.exclude(val) }
30
33
  end
31
34
 
32
- def drb_argv
33
- DrbOptions.new(options, filter_manager).options
34
- end
35
-
36
- def filter_manager
37
- @filter_manager ||= RSpec::configuration.filter_manager
38
- end
35
+ # @return [Hash] the final merged options, drawn from all external sources
36
+ attr_reader :options
39
37
 
40
38
  private
41
39
 
40
+ def organize_options
41
+ @filter_manager_inclusions = []
42
+ @filter_manager_exclusions = []
43
+
44
+ @options = (file_options << command_line_options << env_options).each { |opts|
45
+ @filter_manager_inclusions << opts.delete(:inclusion_filter) if opts.key?(:inclusion_filter)
46
+ @filter_manager_exclusions << opts.delete(:exclusion_filter) if opts.key?(:exclusion_filter)
47
+ }.inject(:libs => [], :requires => []) { |hash, opts|
48
+ hash.merge(opts) { |key, oldval, newval|
49
+ [:libs, :requires].include?(key) ? oldval + newval : newval
50
+ }
51
+ }
52
+ end
53
+
42
54
  UNFORCED_OPTIONS = [
43
55
  :requires, :profile, :drb, :libs, :files_or_directories_to_run,
44
- :line_numbers, :full_description, :full_backtrace, :tty
56
+ :full_description, :full_backtrace, :tty
45
57
  ].to_set
46
58
 
47
59
  UNPROCESSABLE_OPTIONS = [:formatters].to_set
@@ -58,6 +70,10 @@ module RSpec
58
70
  end
59
71
 
60
72
  OPTIONS_ORDER = [
73
+ # It's important to set this before anything that might issue a
74
+ # deprecation (or otherwise access the reporter).
75
+ :deprecation_stream,
76
+
61
77
  # load paths depend on nothing, but must be set before `requires`
62
78
  # to support load-path-relative requires.
63
79
  :libs,
@@ -0,0 +1,111 @@
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 << "--profile" if @submitted_options[:profile_examples]
45
+ argv << "--backtrace" if @submitted_options[:full_backtrace]
46
+ argv << "--tty" if @submitted_options[:tty]
47
+ argv << "--fail-fast" if @submitted_options[:fail_fast]
48
+ argv << "--options" << @submitted_options[:custom_options_file] if @submitted_options[:custom_options_file]
49
+ argv << "--order" << @submitted_options[:order] if @submitted_options[:order]
50
+
51
+ add_failure_exit_code(argv)
52
+ add_full_description(argv)
53
+ add_filter(argv, :inclusion, @filter_manager.inclusions)
54
+ add_filter(argv, :exclusion, @filter_manager.exclusions)
55
+ add_formatters(argv)
56
+ add_libs(argv)
57
+ add_requires(argv)
58
+
59
+ argv + @submitted_options[:files_or_directories_to_run]
60
+ end
61
+
62
+ def add_failure_exit_code(argv)
63
+ if @submitted_options[:failure_exit_code]
64
+ argv << "--failure-exit-code" << @submitted_options[:failure_exit_code].to_s
65
+ end
66
+ end
67
+
68
+ def add_full_description(argv)
69
+ if @submitted_options[:full_description]
70
+ # The argument to --example is regexp-escaped before being stuffed
71
+ # into a regexp when received for the first time (see OptionParser).
72
+ # Hence, merely grabbing the source of this regexp will retain the
73
+ # backslashes, so we must remove them.
74
+ @submitted_options[:full_description].each do |description|
75
+ argv << "--example" << description.source.delete('\\')
76
+ end
77
+ end
78
+ end
79
+
80
+ CONDITIONAL_FILTERS = [:if, :unless]
81
+
82
+ def add_filter(argv, name, hash)
83
+ hash.each_pair do |k, v|
84
+ next if CONDITIONAL_FILTERS.include?(k)
85
+ tag = name == :inclusion ? k.to_s : "~#{k}"
86
+ tag << ":#{v}" if v.is_a?(String)
87
+ argv << "--tag" << tag
88
+ end unless hash.empty?
89
+ end
90
+
91
+ def add_formatters(argv)
92
+ @submitted_options[:formatters].each do |pair|
93
+ argv << "--format" << pair[0]
94
+ argv << "--out" << pair[1] if pair[1]
95
+ end if @submitted_options[:formatters]
96
+ end
97
+
98
+ def add_libs(argv)
99
+ @submitted_options[:libs].each do |path|
100
+ argv << "-I" << path
101
+ end if @submitted_options[:libs]
102
+ end
103
+
104
+ def add_requires(argv)
105
+ @submitted_options[:requires].each do |path|
106
+ argv << "--require" << path
107
+ end if @submitted_options[:requires]
108
+ end
109
+ end
110
+ end
111
+ end
@@ -33,11 +33,12 @@ module RSpec
33
33
  @exposed_globally ||= false
34
34
  end
35
35
 
36
+ # @private
36
37
  def self.expose_example_group_alias(name)
37
38
  example_group_aliases << name
38
39
 
39
40
  (class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block|
40
- RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block).register
41
+ RSpec.world.register RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block)
41
42
  end
42
43
 
43
44
  expose_example_group_alias_globally(name) if exposed_globally?
@@ -48,7 +49,8 @@ module RSpec
48
49
  attr_accessor :top_level
49
50
  end
50
51
 
51
- # Add's the describe method to Module and the top level binding
52
+ # Adds the describe method to Module and the top level binding
53
+ # @api private
52
54
  def self.expose_globally!
53
55
  return if exposed_globally?
54
56
 
@@ -59,6 +61,8 @@ module RSpec
59
61
  @exposed_globally = true
60
62
  end
61
63
 
64
+ # Removes the describe method from Module and the top level binding
65
+ # @api private
62
66
  def self.remove_globally!
63
67
  return unless exposed_globally?
64
68
 
@@ -69,12 +73,14 @@ module RSpec
69
73
  @exposed_globally = false
70
74
  end
71
75
 
76
+ # @private
72
77
  def self.expose_example_group_alias_globally(method_name)
73
78
  change_global_dsl do
74
79
  define_method(method_name) { |*a, &b| ::RSpec.__send__(method_name, *a, &b) }
75
80
  end
76
81
  end
77
82
 
83
+ # @private
78
84
  def self.change_global_dsl(&changes)
79
85
  (class << top_level; self; end).class_exec(&changes)
80
86
  Module.class_exec(&changes)
@@ -1,9 +1,15 @@
1
1
  module RSpec
2
2
  module Core
3
3
  # Wrapper for an instance of a subclass of {ExampleGroup}. An instance of
4
- # `Example` is returned by the {ExampleGroup#example example} method
5
- # exposed to examples, {Hooks#before before} and {Hooks#after after} hooks,
6
- # and yielded to {Hooks#around around} hooks.
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 inadvertantly redefine.
7
13
  #
8
14
  # Useful for configuring logging and/or taking some action based
9
15
  # on the state of an example's metadata.
@@ -11,17 +17,17 @@ module RSpec
11
17
  # @example
12
18
  #
13
19
  # RSpec.configure do |config|
14
- # config.before do
20
+ # config.before do |example|
15
21
  # log example.description
16
22
  # end
17
23
  #
18
- # config.after do
24
+ # config.after do |example|
19
25
  # log example.description
20
26
  # end
21
27
  #
22
- # config.around do |ex|
28
+ # config.around do |example|
23
29
  # log example.description
24
- # ex.run
30
+ # example.run
25
31
  # end
26
32
  # end
27
33
  #
@@ -33,16 +39,33 @@ module RSpec
33
39
  # end
34
40
  #
35
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`.
36
44
  class Example
37
45
  # @private
38
46
  #
39
47
  # Used to define methods that delegate to this example's metadata
40
- def self.delegate_to_metadata(*keys)
41
- keys.each { |key| define_method(key) { @metadata[key] } }
48
+ def self.delegate_to_metadata(key)
49
+ define_method(key) { @metadata[key] }
42
50
  end
43
51
 
44
- delegate_to_metadata :execution_result, :file_path, :full_description,
45
- :location, :pending, :skip
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 defined.
55
+ delegate_to_metadata :file_path
56
+ # @return [String] the full description (including the docstrings of
57
+ # all parent example groups).
58
+ delegate_to_metadata :full_description
59
+ # @return [String] the exact source location of this example in a form
60
+ # like `./path/to/spec.rb:17`
61
+ delegate_to_metadata :location
62
+ # @return [Boolean] flag that indicates that the example is not expected to pass.
63
+ # It will be run and will either have a pending result (if a failure occurs)
64
+ # or a failed result (if no failure occurs).
65
+ delegate_to_metadata :pending
66
+ # @return [Boolean] flag that will cause the example to not run.
67
+ # The {ExecutionResult} status will be `:pending`.
68
+ delegate_to_metadata :skip
46
69
 
47
70
  # Returns the string submitted to `example` or its aliases (e.g.
48
71
  # `specify`, `it`, etc). If no string is submitted (e.g. `it { is_expected.to
@@ -74,27 +97,28 @@ module RSpec
74
97
  # running this example.
75
98
  attr_reader :example_group_instance
76
99
 
77
- # @attr_accessor
100
+ # @attr
78
101
  # @private
79
102
  attr_accessor :clock
80
103
 
81
104
  # Creates a new instance of Example.
82
- # @param example_group_class the subclass of ExampleGroup in which this Example is declared
83
- # @param description the String passed to the `it` method (or alias)
84
- # @param metadata additional args passed to `it` to be used as metadata
85
- # @param example_block the block of code that represents the example
86
- def initialize(example_group_class, description, metadata, example_block=nil)
87
- @example_group_class, @options, @example_block = example_group_class, metadata, example_block
88
- @metadata = @example_group_class.metadata.for_example(description, metadata)
105
+ # @param example_group_class [Class] the subclass of ExampleGroup in which this Example is declared
106
+ # @param description [String] the String passed to the `it` method (or alias)
107
+ # @param user_metadata [Hash] additional args passed to `it` to be used as metadata
108
+ # @param example_block [Proc] the block of code that represents the example
109
+ # @api private
110
+ def initialize(example_group_class, description, user_metadata, example_block=nil)
111
+ @example_group_class = example_group_class
112
+ @example_block = example_block
113
+
114
+ @metadata = Metadata::ExampleHash.create(
115
+ @example_group_class.metadata, user_metadata, description, example_block
116
+ )
117
+
89
118
  @example_group_instance = @exception = nil
90
119
  @clock = RSpec::Core::Time
91
120
  end
92
121
 
93
- # @deprecated access options via metadata instead
94
- def options
95
- @options
96
- end
97
-
98
122
  # Returns the example group class that provides the context for running
99
123
  # this example.
100
124
  def example_group
@@ -105,7 +129,7 @@ module RSpec
105
129
  alias_method :skipped?, :skip
106
130
 
107
131
  # @api private
108
- # instance_evals the block passed to the constructor in the context of
132
+ # instance_execs the block passed to the constructor in the context of
109
133
  # the instance of {ExampleGroup}.
110
134
  # @param example_group_instance the instance of an ExampleGroup subclass
111
135
  def run(example_group_instance, reporter)
@@ -118,9 +142,9 @@ module RSpec
118
142
  if skipped?
119
143
  Pending.mark_pending! self, skip
120
144
  elsif !RSpec.configuration.dry_run?
121
- with_around_each_hooks do
145
+ with_around_example_hooks do
122
146
  begin
123
- run_before_each
147
+ run_before_example
124
148
  @example_group_instance.instance_exec(self, &@example_block)
125
149
 
126
150
  if pending?
@@ -128,7 +152,7 @@ module RSpec
128
152
 
129
153
  raise Pending::PendingExampleFixedError,
130
154
  'Expected example to fail since it is pending, but it passed.',
131
- metadata[:caller]
155
+ [location]
132
156
  end
133
157
  rescue Pending::SkipDeclaredInExample
134
158
  # no-op, required metadata has already been set by the `skip`
@@ -136,7 +160,7 @@ module RSpec
136
160
  rescue Exception => e
137
161
  set_exception(e)
138
162
  ensure
139
- run_after_each
163
+ run_after_example
140
164
  end
141
165
  end
142
166
  end
@@ -147,12 +171,6 @@ module RSpec
147
171
  @example_group_instance.instance_variable_set(ivar, nil)
148
172
  end
149
173
  @example_group_instance = nil
150
-
151
- begin
152
- assign_generated_description
153
- rescue Exception => e
154
- set_exception(e, "while assigning the example description")
155
- end
156
174
  end
157
175
 
158
176
  finish(reporter)
@@ -160,12 +178,10 @@ module RSpec
160
178
  RSpec.current_example = nil
161
179
  end
162
180
 
163
- # Wraps a `Proc` and exposes a `run` method for use in {Hooks#around
164
- # around} hooks.
165
- #
166
- # @note Procsy, itself, is not a public API, but we're documenting it
167
- # here to document how to interact with the object yielded to an
168
- # `around` hook.
181
+ # Wraps both a `Proc` and an {Example} for use in {Hooks#around
182
+ # around} hooks. In around hooks we need to yield this special
183
+ # kind of object (rather than the raw {Example}) because when
184
+ # there are multiple `around` hooks we have to wrap them recursively.
169
185
  #
170
186
  # @example
171
187
  #
@@ -177,39 +193,46 @@ module RSpec
177
193
  # ex.run # run delegates to ex.call
178
194
  # end
179
195
  # end
196
+ #
197
+ # @note This class also exposes the instance methods of {Example},
198
+ # proxying them through to the wrapped {Example} instance.
180
199
  class Procsy
181
- # The `metadata` of the {Example} instance.
182
- attr_reader :metadata
200
+ # The {Example} instance.
201
+ attr_reader :example
202
+
203
+ Example.public_instance_methods(false).each do |name|
204
+ define_method(name) { |*a, &b| @example.__send__(name, *a, &b) }
205
+ end
183
206
 
184
207
  Proc.public_instance_methods(false).each do |name|
185
208
  define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) }
186
209
  end
187
210
  alias run call
188
211
 
189
- def initialize(metadata, &block)
190
- @metadata = metadata
191
- @proc = block
212
+ def initialize(example, &block)
213
+ @example = example
214
+ @proc = block
192
215
  end
193
216
 
194
- # @api private
217
+ # @private
195
218
  def wrap(&block)
196
- self.class.new(metadata, &block)
219
+ self.class.new(example, &block)
197
220
  end
198
221
  end
199
222
 
200
223
  # @private
201
224
  def any_apply?(filters)
202
- metadata.any_apply?(filters)
225
+ MetadataFilter.any_apply?(filters, metadata)
203
226
  end
204
227
 
205
228
  # @private
206
229
  def all_apply?(filters)
207
- @metadata.all_apply?(filters) || @example_group_class.all_apply?(filters)
230
+ MetadataFilter.all_apply?(filters, metadata) || @example_group_class.all_apply?(filters)
208
231
  end
209
232
 
210
233
  # @private
211
- def around_each_hooks
212
- @around_each_hooks ||= example_group.hooks.around_each_hooks_for(self)
234
+ def around_example_hooks
235
+ @around_example_hooks ||= example_group.hooks.around_example_hooks_for(self)
213
236
  end
214
237
 
215
238
  # @private
@@ -217,10 +240,10 @@ module RSpec
217
240
  # Used internally to set an exception in an after hook, which
218
241
  # captures the exception but doesn't raise it.
219
242
  def set_exception(exception, context=nil)
220
- if pending?
221
- metadata[:execution_result][:pending_exception] = exception
243
+ if pending? && !(Pending::PendingExampleFixedError === exception)
244
+ execution_result.pending_exception = exception
222
245
  else
223
- if @exception && context != :dont_print
246
+ if @exception
224
247
  # An error has already been set; we don't want to override it,
225
248
  # but we also don't want silence the error, so let's print it.
226
249
  msg = <<-EOS
@@ -240,7 +263,7 @@ module RSpec
240
263
  # @private
241
264
  #
242
265
  # Used internally to set an exception and fail without actually executing
243
- # the example when an exception is raised in before(:all).
266
+ # the example when an exception is raised in before(:context).
244
267
  def fail_with_exception(reporter, exception)
245
268
  start(reporter)
246
269
  set_exception(exception)
@@ -250,7 +273,7 @@ module RSpec
250
273
  # @private
251
274
  #
252
275
  # Used internally to skip without actually executing the example when
253
- # skip is used in before(:all)
276
+ # skip is used in before(:context)
254
277
  def skip_with_exception(reporter, exception)
255
278
  start(reporter)
256
279
  Pending.mark_skipped! self, exception.argument
@@ -258,8 +281,10 @@ module RSpec
258
281
  end
259
282
 
260
283
  # @private
261
- def instance_exec_with_rescue(context = nil, &block)
262
- @example_group_instance.instance_exec_with_rescue(self, context, &block)
284
+ def instance_exec_with_rescue(context, &block)
285
+ @example_group_instance.instance_exec(self, &block)
286
+ rescue Exception => e
287
+ set_exception(e, context)
263
288
  end
264
289
 
265
290
  # @private
@@ -269,88 +294,86 @@ module RSpec
269
294
 
270
295
  private
271
296
 
272
- def with_around_each_hooks(&block)
273
- if around_each_hooks.empty?
297
+ def with_around_example_hooks(&block)
298
+ if around_example_hooks.empty?
274
299
  yield
275
300
  else
276
- @example_group_class.hooks.run(:around, :each, self, Procsy.new(metadata, &block))
301
+ @example_group_class.hooks.run(:around, :example, self, Procsy.new(self, &block))
277
302
  end
278
303
  rescue Exception => e
279
- set_exception(e, "in an around(:each) hook")
304
+ set_exception(e, "in an `around(:example)` hook")
280
305
  end
281
306
 
282
307
  def start(reporter)
283
308
  reporter.example_started(self)
284
- record :started_at => clock.now
309
+ execution_result.started_at = clock.now
285
310
  end
286
311
 
287
312
  def finish(reporter)
288
- pending_message = metadata[:execution_result][:pending_message]
313
+ pending_message = execution_result.pending_message
289
314
 
290
315
  if @exception
291
- record_finished 'failed', :exception => @exception
316
+ record_finished :failed
317
+ execution_result.exception = @exception
292
318
  reporter.example_failed self
293
319
  false
294
320
  elsif pending_message
295
- record_finished 'pending', :pending_message => pending_message
321
+ record_finished :pending
322
+ execution_result.pending_message = pending_message
296
323
  reporter.example_pending self
297
324
  true
298
325
  else
299
- record_finished 'passed'
326
+ record_finished :passed
300
327
  reporter.example_passed self
301
328
  true
302
329
  end
303
330
  end
304
331
 
305
- def record_finished(status, results={})
306
- finished_at = clock.now
307
- record results.merge(
308
- :status => status,
309
- :finished_at => finished_at,
310
- :run_time => (finished_at - execution_result[:started_at]).to_f
311
- )
332
+ def record_finished(status)
333
+ execution_result.record_finished(status, clock.now)
312
334
  end
313
335
 
314
- def run_before_each
336
+ def run_before_example
315
337
  @example_group_instance.setup_mocks_for_rspec
316
- @example_group_class.hooks.run(:before, :each, self)
338
+ @example_group_class.hooks.run(:before, :example, self)
317
339
  end
318
340
 
319
- def run_after_each
320
- @example_group_class.hooks.run(:after, :each, self)
341
+ def run_after_example
342
+ @example_group_class.hooks.run(:after, :example, self)
321
343
  verify_mocks
344
+ assign_generated_description if RSpec.configuration.expecting_with_rspec?
322
345
  rescue Exception => e
323
- set_exception(e, "in an after(:each) hook")
346
+ set_exception(e, "in an `after(:example)` hook")
324
347
  ensure
325
348
  @example_group_instance.teardown_mocks_for_rspec
326
349
  end
327
350
 
328
351
  def verify_mocks
329
- @example_group_instance.verify_mocks_for_rspec
352
+ @example_group_instance.verify_mocks_for_rspec if mocks_need_verification?
330
353
  rescue Exception => e
331
- if metadata[:execution_result][:pending_message]
332
- metadata[:execution_result][:pending_fixed] = false
333
- metadata[:pending] = true
354
+ if pending?
355
+ execution_result.pending_fixed = false
334
356
  @exception = nil
335
357
  else
336
- set_exception(e, :dont_print)
358
+ set_exception(e)
337
359
  end
338
360
  end
339
361
 
340
- def assign_generated_description
341
- return unless RSpec.configuration.expecting_with_rspec?
362
+ def mocks_need_verification?
363
+ exception.nil? || execution_result.pending_fixed?
364
+ end
342
365
 
343
- if metadata[:description_args].empty?
344
- metadata[:description_args] << RSpec::Matchers.generated_description
366
+ def assign_generated_description
367
+ if metadata[:description].empty? && (description = RSpec::Matchers.generated_description)
368
+ metadata[:description] = description
369
+ metadata[:full_description] << description
345
370
  end
346
-
371
+ rescue Exception => e
372
+ set_exception(e, "while assigning the example description")
373
+ ensure
347
374
  RSpec::Matchers.clear_generated_description
348
375
  end
349
376
 
350
- def record(results={})
351
- execution_result.update(results)
352
- end
353
-
354
377
  def skip_message
355
378
  if String === skip
356
379
  skip
@@ -358,6 +381,92 @@ module RSpec
358
381
  Pending::NO_REASON_GIVEN
359
382
  end
360
383
  end
384
+
385
+ # Represents the result of executing an example.
386
+ # Behaves like a hash for backwards compatibility.
387
+ class ExecutionResult
388
+ include HashImitatable
389
+
390
+ # @return [Symbol] `:passed`, `:failed` or `:pending`.
391
+ attr_accessor :status
392
+
393
+ # @return [Exception, nil] The failure, if there was one.
394
+ attr_accessor :exception
395
+
396
+ # @return [Time] When the example started.
397
+ attr_accessor :started_at
398
+
399
+ # @return [Time] When the example finished.
400
+ attr_accessor :finished_at
401
+
402
+ # @return [Float] How long the example took in seconds.
403
+ attr_accessor :run_time
404
+
405
+ # @return [String, nil] The reason the example was pending,
406
+ # or nil if the example was not pending.
407
+ attr_accessor :pending_message
408
+
409
+ # @return [Exception, nil] The exception triggered while
410
+ # executing the pending example. If no exception was triggered
411
+ # it would no longer get a status of `:pending` unless it was
412
+ # tagged with `:skip`.
413
+ attr_accessor :pending_exception
414
+
415
+ # @return [Boolean] For examples tagged with `:pending`,
416
+ # this indicates whether or not it now passes.
417
+ attr_accessor :pending_fixed
418
+
419
+ alias pending_fixed? pending_fixed
420
+
421
+ # @api private
422
+ # Records the finished status of the example.
423
+ def record_finished(status, finished_at)
424
+ self.status = status
425
+ self.finished_at = finished_at
426
+ self.run_time = (finished_at - started_at).to_f
427
+ end
428
+
429
+ private
430
+
431
+ # For backwards compatibility we present `status` as a string
432
+ # when presenting the legacy hash interface.
433
+ def hash_for_delegation
434
+ super.tap do |hash|
435
+ hash[:status] &&= status.to_s
436
+ end
437
+ end
438
+
439
+ def set_value(name, value)
440
+ value &&= value.to_sym if name == :status
441
+ super(name, value)
442
+ end
443
+
444
+ def get_value(name)
445
+ if name == :status
446
+ status.to_s if status
447
+ else
448
+ super
449
+ end
450
+ end
451
+
452
+ def issue_deprecation(method_name, *args)
453
+ RSpec.deprecate("Treating `metadata[:execution_result]` as a hash",
454
+ :replacement => "the attributes methods to access the data")
455
+ end
456
+ end
457
+ end
458
+
459
+ # @private
460
+ # Provides an execution context for before/after :suite hooks.
461
+ class SuiteHookContext < Example
462
+ def initialize
463
+ super(AnonymousExampleGroup, "", {})
464
+ end
465
+
466
+ # To ensure we don't silence errors...
467
+ def set_exception(exception, context=nil)
468
+ raise exception
469
+ end
361
470
  end
362
471
  end
363
472
  end