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
@@ -0,0 +1,138 @@
1
+ require 'stringio'
2
+ RSpec::Support.require_rspec_core "formatters/base_bisect_formatter"
3
+ RSpec::Support.require_rspec_core "bisect/utilities"
4
+
5
+ module RSpec
6
+ module Core
7
+ module Bisect
8
+ # A Bisect runner that runs requested subsets of the suite by forking
9
+ # sub-processes. The main process bootstraps RSpec and the application
10
+ # environment (including preloading files specified via `--require`) so
11
+ # that the individual spec runs do not have to re-pay that cost. Each
12
+ # spec run happens in a forked process, ensuring that the spec files are
13
+ # not loaded in the main process.
14
+ #
15
+ # For most projects, bisections that use `ForkRunner` instead of
16
+ # `ShellRunner` will finish significantly faster, because the `ShellRunner`
17
+ # pays the cost of booting RSpec and the app environment on _every_ run of
18
+ # a subset. In contrast, `ForkRunner` pays that cost only once.
19
+ #
20
+ # However, not all projects can use `ForkRunner`. Obviously, on platforms
21
+ # that do not support forking (e.g. Windows), it cannot be used. In addition,
22
+ # it can cause problems for some projects that put side-effectful spec
23
+ # bootstrapping logic that should run on every spec run directly at the top
24
+ # level in a file loaded by `--require`, rather than in a `before(:suite)`
25
+ # hook. For example, consider a project that relies on some top-level logic
26
+ # in `spec_helper` to boot a Redis server for the test suite, intending the
27
+ # Redis bootstrapping to happen on every spec run. With `ShellRunner`, the
28
+ # bootstrapping logic will happen for each run of any subset of the suite,
29
+ # but for `ForkRunner`, such logic will only get run once, when the
30
+ # `RunDispatcher` boots the application environment. This might cause
31
+ # problems. The solution is for users to move the bootstrapping logic into
32
+ # a `before(:suite)` hook, or use the slower `ShellRunner`.
33
+ #
34
+ # @private
35
+ class ForkRunner
36
+ def self.start(shell_command, spec_runner)
37
+ instance = new(shell_command, spec_runner)
38
+ yield instance
39
+ ensure
40
+ instance.shutdown
41
+ end
42
+
43
+ def self.name
44
+ :fork
45
+ end
46
+
47
+ def initialize(shell_command, spec_runner)
48
+ @shell_command = shell_command
49
+ @channel = Channel.new
50
+ @run_dispatcher = RunDispatcher.new(spec_runner, @channel)
51
+ end
52
+
53
+ def run(locations)
54
+ run_descriptor = ExampleSetDescriptor.new(locations, original_results.failed_example_ids)
55
+ dispatch_run(run_descriptor)
56
+ end
57
+
58
+ def original_results
59
+ @original_results ||= dispatch_run(ExampleSetDescriptor.new(
60
+ @shell_command.original_locations, []))
61
+ end
62
+
63
+ def shutdown
64
+ @channel.close
65
+ end
66
+
67
+ private
68
+
69
+ def dispatch_run(run_descriptor)
70
+ @run_dispatcher.dispatch_specs(run_descriptor)
71
+ @channel.receive.tap do |result|
72
+ if result.is_a?(String)
73
+ raise BisectFailedError.for_failed_spec_run(result)
74
+ end
75
+ end
76
+ end
77
+
78
+ # @private
79
+ class RunDispatcher
80
+ def initialize(runner, channel)
81
+ @runner = runner
82
+ @channel = channel
83
+
84
+ @spec_output = StringIO.new
85
+
86
+ runner.configuration.tap do |c|
87
+ c.reset_reporter
88
+ c.output_stream = @spec_output
89
+ c.error_stream = @spec_output
90
+ end
91
+ end
92
+
93
+ def dispatch_specs(run_descriptor)
94
+ pid = fork { run_specs(run_descriptor) }
95
+ # We don't use Process.waitpid here as it was causing bisects to
96
+ # block due to the file descriptor limit on OSX / Linux. We need
97
+ # to detach the process to avoid having zombie processes
98
+ # consuming slots in the kernel process table during bisect runs.
99
+ Process.detach(pid)
100
+ end
101
+
102
+ private
103
+
104
+ def run_specs(run_descriptor)
105
+ $stdout = $stderr = @spec_output
106
+ formatter = CaptureFormatter.new(run_descriptor.failed_example_ids)
107
+
108
+ @runner.configuration.tap do |c|
109
+ c.files_or_directories_to_run = run_descriptor.all_example_ids
110
+ c.formatter = formatter
111
+ c.load_spec_files
112
+ end
113
+
114
+ # `announce_filters` has the side effect of implementing the logic
115
+ # that honors `config.run_all_when_everything_filtered` so we need
116
+ # to call it here. When we remove `run_all_when_everything_filtered`
117
+ # (slated for RSpec 4), we can remove this call to `announce_filters`.
118
+ @runner.world.announce_filters
119
+
120
+ @runner.run_specs(@runner.world.ordered_example_groups)
121
+ latest_run_results = formatter.results
122
+
123
+ if latest_run_results.nil? || latest_run_results.all_example_ids.empty?
124
+ @channel.send(@spec_output.string)
125
+ else
126
+ @channel.send(latest_run_results)
127
+ end
128
+ end
129
+ end
130
+
131
+ class CaptureFormatter < Formatters::BaseBisectFormatter
132
+ attr_accessor :results
133
+ alias_method :notify_results, :results=
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,61 @@
1
+ require 'drb/drb'
2
+ require 'drb/acl'
3
+ RSpec::Support.require_rspec_core "bisect/utilities"
4
+
5
+ module RSpec
6
+ module Core
7
+ # @private
8
+ module Bisect
9
+ # @private
10
+ # A DRb server that receives run results from a separate RSpec process
11
+ # started by the bisect process.
12
+ class Server
13
+ def self.run
14
+ server = new
15
+ server.start
16
+ yield server
17
+ ensure
18
+ server.stop
19
+ end
20
+
21
+ def capture_run_results(files_or_directories_to_run=[], expected_failures=[])
22
+ self.expected_failures = expected_failures
23
+ self.files_or_directories_to_run = files_or_directories_to_run
24
+ self.latest_run_results = nil
25
+ run_output = yield
26
+
27
+ if latest_run_results.nil? || latest_run_results.all_example_ids.empty?
28
+ raise BisectFailedError.for_failed_spec_run(run_output)
29
+ end
30
+
31
+ latest_run_results
32
+ end
33
+
34
+ def start
35
+ # Only allow remote DRb requests from this machine.
36
+ DRb.install_acl ACL.new(%w[ deny all allow localhost allow 127.0.0.1 allow ::1 ])
37
+
38
+ # We pass `nil` as the first arg to allow it to pick a DRb port.
39
+ @drb = DRb.start_service(nil, self)
40
+ end
41
+
42
+ def stop
43
+ @drb.stop_service
44
+ end
45
+
46
+ def drb_port
47
+ @drb_port ||= Integer(@drb.uri[/\d+$/])
48
+ end
49
+
50
+ # Fetched via DRb by the BisectDRbFormatter to determine when to abort.
51
+ attr_accessor :expected_failures
52
+
53
+ # Set via DRb by the BisectDRbFormatter with the results of the run.
54
+ attr_accessor :latest_run_results
55
+
56
+ # Fetched via DRb to tell clients which files to run
57
+ attr_accessor :files_or_directories_to_run
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,126 @@
1
+ RSpec::Support.require_rspec_core "shell_escape"
2
+ require 'shellwords'
3
+
4
+ module RSpec
5
+ module Core
6
+ module Bisect
7
+ # Provides an API to generate shell commands to run the suite for a
8
+ # set of locations, using the given bisect server to capture the results.
9
+ # @private
10
+ class ShellCommand
11
+ attr_reader :original_cli_args
12
+
13
+ def initialize(original_cli_args)
14
+ @original_cli_args = original_cli_args.reject { |arg| arg.start_with?("--bisect") }
15
+ end
16
+
17
+ def command_for(locations, server)
18
+ parts = []
19
+
20
+ parts << RUBY << load_path
21
+ parts << open3_safe_escape(RSpec::Core.path_to_executable)
22
+
23
+ parts << "--format" << "bisect-drb"
24
+ parts << "--drb-port" << server.drb_port
25
+
26
+ parts.concat(reusable_cli_options)
27
+ parts.concat(locations.map { |l| open3_safe_escape(l) })
28
+
29
+ parts.join(" ")
30
+ end
31
+
32
+ def repro_command_from(locations)
33
+ parts = []
34
+
35
+ parts.concat environment_repro_parts
36
+ parts << "rspec"
37
+ parts.concat Formatters::Helpers.organize_ids(locations)
38
+ parts.concat original_cli_args_without_locations
39
+
40
+ parts.join(" ")
41
+ end
42
+
43
+ def original_locations
44
+ parsed_original_cli_options.fetch(:files_or_directories_to_run)
45
+ end
46
+
47
+ def bisect_environment_hash
48
+ if ENV.key?('SPEC_OPTS')
49
+ { 'SPEC_OPTS' => spec_opts_without_bisect }
50
+ else
51
+ {}
52
+ end
53
+ end
54
+
55
+ def spec_opts_without_bisect
56
+ Shellwords.join(
57
+ Shellwords.split(ENV.fetch('SPEC_OPTS', '')).reject do |arg|
58
+ arg =~ /^--bisect/
59
+ end
60
+ )
61
+ end
62
+
63
+ private
64
+
65
+ include RSpec::Core::ShellEscape
66
+ # On JRuby, Open3.popen3 does not handle shellescaped args properly:
67
+ # https://github.com/jruby/jruby/issues/2767
68
+ if RSpec::Support::Ruby.jruby?
69
+ # :nocov:
70
+ alias open3_safe_escape quote
71
+ # :nocov:
72
+ else
73
+ alias open3_safe_escape escape
74
+ end
75
+
76
+ def environment_repro_parts
77
+ bisect_environment_hash.map do |k, v|
78
+ %Q(#{k}="#{v}")
79
+ end
80
+ end
81
+
82
+ def reusable_cli_options
83
+ @reusable_cli_options ||= begin
84
+ opts = original_cli_args_without_locations
85
+
86
+ if (port = parsed_original_cli_options[:drb_port])
87
+ opts -= %W[ --drb-port #{port} ]
88
+ end
89
+
90
+ parsed_original_cli_options.fetch(:formatters) { [] }.each do |(name, out)|
91
+ opts -= %W[ --format #{name} -f -f#{name} ]
92
+ opts -= %W[ --out #{out} -o -o#{out} ]
93
+ end
94
+
95
+ opts
96
+ end
97
+ end
98
+
99
+ def original_cli_args_without_locations
100
+ @original_cli_args_without_locations ||= begin
101
+ files_or_dirs = parsed_original_cli_options.fetch(:files_or_directories_to_run)
102
+ @original_cli_args - files_or_dirs
103
+ end
104
+ end
105
+
106
+ def parsed_original_cli_options
107
+ @parsed_original_cli_options ||= Parser.parse(@original_cli_args)
108
+ end
109
+
110
+ def load_path
111
+ @load_path ||= "-I#{$LOAD_PATH.map { |p| open3_safe_escape(p) }.join(':')}"
112
+ end
113
+
114
+ # Path to the currently running Ruby executable, borrowed from Rake:
115
+ # https://github.com/ruby/rake/blob/v10.4.2/lib/rake/file_utils.rb#L8-L12
116
+ # Note that we skip `ENV['RUBY']` because we don't have to deal with running
117
+ # RSpec from within a MRI source repository:
118
+ # https://github.com/ruby/rake/commit/968682759b3b65e42748cd2befb2ff3e982272d9
119
+ RUBY = File.join(
120
+ RbConfig::CONFIG['bindir'],
121
+ RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']).
122
+ sub(/.*\s.*/m, '"\&"')
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,73 @@
1
+ require 'open3'
2
+ RSpec::Support.require_rspec_core "bisect/server"
3
+
4
+ module RSpec
5
+ module Core
6
+ module Bisect
7
+ # Provides an API to run the suite for a set of locations, using
8
+ # the given bisect server to capture the results.
9
+ #
10
+ # Sets of specs are run by shelling out.
11
+ # @private
12
+ class ShellRunner
13
+ def self.start(shell_command, _spec_runner)
14
+ Server.run do |server|
15
+ yield new(server, shell_command)
16
+ end
17
+ end
18
+
19
+ def self.name
20
+ :shell
21
+ end
22
+
23
+ def initialize(server, shell_command)
24
+ @server = server
25
+ @shell_command = shell_command
26
+ end
27
+
28
+ def run(locations)
29
+ run_locations(locations, original_results.failed_example_ids)
30
+ end
31
+
32
+ def original_results
33
+ @original_results ||= run_locations(@shell_command.original_locations)
34
+ end
35
+
36
+ private
37
+
38
+ def run_locations(*capture_args)
39
+ @server.capture_run_results(*capture_args) do
40
+ run_command @shell_command.command_for([], @server)
41
+ end
42
+ end
43
+
44
+ # `Open3.capture2e` does not work on JRuby:
45
+ # https://github.com/jruby/jruby/issues/2766
46
+ if Open3.respond_to?(:capture2e) && !RSpec::Support::Ruby.jruby?
47
+ def run_command(cmd)
48
+ Open3.capture2e(@shell_command.bisect_environment_hash, cmd).first
49
+ end
50
+ else # for 1.8.7
51
+ # :nocov:
52
+ def run_command(cmd)
53
+ out = err = nil
54
+
55
+ original_spec_opts = ENV['SPEC_OPTS']
56
+ ENV['SPEC_OPTS'] = @shell_command.spec_opts_without_bisect
57
+
58
+ Open3.popen3(cmd) do |_, stdout, stderr|
59
+ # Reading the streams blocks until the process is complete
60
+ out = stdout.read
61
+ err = stderr.read
62
+ end
63
+
64
+ "Stdout:\n#{out}\n\nStderr:\n#{err}"
65
+ ensure
66
+ ENV['SPEC_OPTS'] = original_spec_opts
67
+ end
68
+ # :nocov:
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,69 @@
1
+ module RSpec
2
+ module Core
3
+ module Bisect
4
+ # @private
5
+ ExampleSetDescriptor = Struct.new(:all_example_ids, :failed_example_ids)
6
+
7
+ # @private
8
+ class BisectFailedError < StandardError
9
+ def self.for_failed_spec_run(spec_output)
10
+ new("Failed to get results from the spec run. Spec run output:\n\n" +
11
+ spec_output)
12
+ end
13
+ end
14
+
15
+ # Wraps a `formatter` providing a simple means to notify it in place
16
+ # of an `RSpec::Core::Reporter`, without involving configuration in
17
+ # any way.
18
+ # @private
19
+ class Notifier
20
+ def initialize(formatter)
21
+ @formatter = formatter
22
+ end
23
+
24
+ def publish(event, *args)
25
+ return unless @formatter.respond_to?(event)
26
+ notification = Notifications::CustomNotification.for(*args)
27
+ @formatter.__send__(event, notification)
28
+ end
29
+ end
30
+
31
+ # Wraps a pipe to support sending objects between a child and
32
+ # parent process. Where supported, encoding is explicitly
33
+ # set to ensure binary data is able to pass from child to
34
+ # parent.
35
+ # @private
36
+ class Channel
37
+ if String.method_defined?(:encoding)
38
+ MARSHAL_DUMP_ENCODING = Marshal.dump("").encoding
39
+ end
40
+
41
+ def initialize
42
+ @read_io, @write_io = IO.pipe
43
+
44
+ if defined?(MARSHAL_DUMP_ENCODING) && IO.method_defined?(:set_encoding)
45
+ # Ensure the pipe can send any content produced by Marshal.dump
46
+ @write_io.set_encoding MARSHAL_DUMP_ENCODING
47
+ end
48
+ end
49
+
50
+ def send(message)
51
+ packet = Marshal.dump(message)
52
+ @write_io.write("#{packet.bytesize}\n#{packet}")
53
+ end
54
+
55
+ # rubocop:disable Security/MarshalLoad
56
+ def receive
57
+ packet_size = Integer(@read_io.gets)
58
+ Marshal.load(@read_io.read(packet_size))
59
+ end
60
+ # rubocop:enable Security/MarshalLoad
61
+
62
+ def close
63
+ @read_io.close
64
+ @write_io.close
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end