rspec-support 3.0.4 → 3.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/Changelog.md +322 -0
  4. data/{LICENSE.txt → LICENSE.md} +3 -2
  5. data/README.md +29 -6
  6. data/lib/rspec/support/caller_filter.rb +35 -16
  7. data/lib/rspec/support/comparable_version.rb +46 -0
  8. data/lib/rspec/support/differ.rb +51 -41
  9. data/lib/rspec/support/directory_maker.rb +63 -0
  10. data/lib/rspec/support/encoded_string.rb +110 -15
  11. data/lib/rspec/support/fuzzy_matcher.rb +5 -6
  12. data/lib/rspec/support/hunk_generator.rb +0 -1
  13. data/lib/rspec/support/matcher_definition.rb +42 -0
  14. data/lib/rspec/support/method_signature_verifier.rb +287 -54
  15. data/lib/rspec/support/mutex.rb +73 -0
  16. data/lib/rspec/support/object_formatter.rb +275 -0
  17. data/lib/rspec/support/recursive_const_methods.rb +76 -0
  18. data/lib/rspec/support/reentrant_mutex.rb +78 -0
  19. data/lib/rspec/support/ruby_features.rb +177 -14
  20. data/lib/rspec/support/source/location.rb +21 -0
  21. data/lib/rspec/support/source/node.rb +110 -0
  22. data/lib/rspec/support/source/token.rb +94 -0
  23. data/lib/rspec/support/source.rb +85 -0
  24. data/lib/rspec/support/spec/deprecation_helpers.rb +19 -32
  25. data/lib/rspec/support/spec/diff_helpers.rb +31 -0
  26. data/lib/rspec/support/spec/in_sub_process.rb +43 -16
  27. data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
  28. data/lib/rspec/support/spec/shell_out.rb +108 -0
  29. data/lib/rspec/support/spec/stderr_splitter.rb +31 -9
  30. data/lib/rspec/support/spec/string_matcher.rb +45 -0
  31. data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
  32. data/lib/rspec/support/spec/with_isolated_stderr.rb +0 -2
  33. data/lib/rspec/support/spec.rb +46 -26
  34. data/lib/rspec/support/version.rb +1 -1
  35. data/lib/rspec/support/warnings.rb +6 -6
  36. data/lib/rspec/support/with_keywords_when_needed.rb +33 -0
  37. data/lib/rspec/support.rb +87 -3
  38. data.tar.gz.sig +0 -0
  39. metadata +70 -52
  40. metadata.gz.sig +0 -0
  41. data/lib/rspec/support/version_checker.rb +0 -53
@@ -0,0 +1,108 @@
1
+ require 'open3'
2
+ require 'rake/file_utils'
3
+ require 'shellwords'
4
+
5
+ module RSpec
6
+ module Support
7
+ module ShellOut
8
+ def with_env(vars)
9
+ original = ENV.to_hash
10
+ vars.each { |k, v| ENV[k] = v }
11
+
12
+ begin
13
+ yield
14
+ ensure
15
+ ENV.replace(original)
16
+ end
17
+ end
18
+
19
+ if Open3.respond_to?(:capture3) # 1.9+
20
+ def shell_out(*command)
21
+ stdout, stderr, status = Open3.capture3(*command)
22
+ return stdout, filter(stderr), status
23
+ end
24
+ else # 1.8.7
25
+ # popen3 doesn't provide the exit status so we fake it out.
26
+ FakeProcessStatus = Struct.new(:exitstatus)
27
+
28
+ def shell_out(*command)
29
+ stdout = stderr = nil
30
+
31
+ Open3.popen3(*command) do |_in, out, err|
32
+ stdout = out.read
33
+ stderr = err.read
34
+ end
35
+
36
+ status = FakeProcessStatus.new(0)
37
+ return stdout, filter(stderr), status
38
+ end
39
+ end
40
+
41
+ def run_ruby_with_current_load_path(ruby_command, *flags)
42
+ command = [
43
+ FileUtils::RUBY,
44
+ "-I#{$LOAD_PATH.map(&:shellescape).join(File::PATH_SEPARATOR)}",
45
+ "-e", ruby_command, *flags
46
+ ]
47
+
48
+ # Unset these env vars because `ruby -w` will issue warnings whenever
49
+ # they are set to non-default values.
50
+ with_env 'RUBY_GC_HEAP_FREE_SLOTS' => nil, 'RUBY_GC_MALLOC_LIMIT' => nil,
51
+ 'RUBY_FREE_MIN' => nil do
52
+ shell_out(*command)
53
+ end
54
+ end
55
+
56
+ LINES_TO_IGNORE =
57
+ [
58
+ # Ignore bundler warning.
59
+ %r{bundler/source/rubygems},
60
+ # Ignore bundler + rubygems warning.
61
+ %r{site_ruby/\d\.\d\.\d/rubygems},
62
+ %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/rubygems},
63
+ # This is required for windows for some reason
64
+ %r{lib/bundler/rubygems},
65
+ # This is a JRuby file that generates warnings on 9.0.3.0
66
+ %r{lib/ruby/stdlib/jar},
67
+ # This is a JRuby file that generates warnings on 9.1.7.0
68
+ %r{org/jruby/RubyKernel\.java},
69
+ # This is a JRuby gem that generates warnings on 9.1.7.0
70
+ %r{ffi-1\.13\.\d+-java},
71
+ %r{uninitialized constant FFI},
72
+ # These are related to the above, there is a warning about io from FFI
73
+ %r{jruby-\d\.\d\.\d+\.\d/lib/ruby/stdlib/io},
74
+ %r{io/console on JRuby shells out to stty for most operations},
75
+ # This is a JRuby 9.1.17.0 error on Github Actions
76
+ %r{io/console not supported; tty will not be manipulated},
77
+ # This is a JRuby 9.2.1.x error
78
+ %r{jruby/kernel/gem_prelude},
79
+ %r{lib/jruby\.jar!/jruby/preludes},
80
+ # Ignore some JRuby errors for gems
81
+ %r{jruby/\d\.\d(\.\d)?/gems/aruba},
82
+ %r{jruby/\d\.\d(\.\d)?/gems/ffi},
83
+ ]
84
+
85
+ def strip_known_warnings(input)
86
+ input.split("\n").reject do |l|
87
+ LINES_TO_IGNORE.any? { |to_ignore| l =~ to_ignore } ||
88
+ # Remove blank lines
89
+ l == "" || l.nil?
90
+ end.join("\n")
91
+ end
92
+
93
+ private
94
+
95
+ if Ruby.jruby?
96
+ def filter(output)
97
+ output.each_line.reject do |line|
98
+ line.include?("lib/ruby/shared/rubygems")
99
+ end.join($/)
100
+ end
101
+ else
102
+ def filter(output)
103
+ output
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -6,15 +6,16 @@ module RSpec
6
6
  def initialize(original)
7
7
  @orig_stderr = original
8
8
  @output_tracker = ::StringIO.new
9
+ @last_line = nil
9
10
  end
10
11
 
11
12
  respond_to_name = (::RUBY_VERSION.to_f < 1.9) ? :respond_to? : :respond_to_missing?
12
13
  define_method respond_to_name do |*args|
13
- @orig_stderr.respond_to?(*args) || super
14
+ @orig_stderr.respond_to?(*args) || super(*args)
14
15
  end
15
16
 
16
17
  def method_missing(name, *args, &block)
17
- @output_tracker.__send__(name, *args, &block)
18
+ @output_tracker.__send__(name, *args, &block) if @output_tracker.respond_to?(name)
18
19
  @orig_stderr.__send__(name, *args, &block)
19
20
  end
20
21
 
@@ -22,13 +23,35 @@ module RSpec
22
23
  @orig_stderr == other
23
24
  end
24
25
 
26
+ def reopen(*args)
27
+ reset!
28
+ @orig_stderr.reopen(*args)
29
+ end
30
+
31
+ # To work around JRuby error:
32
+ # can't convert RSpec::Support::StdErrSplitter into String
33
+ def to_io
34
+ @orig_stderr.to_io
35
+ end
36
+
25
37
  # To work around JRuby error:
26
38
  # TypeError: $stderr must have write method, RSpec::StdErrSplitter given
27
39
  def write(line)
28
- if line !~ %r{^\S+/gems/\S+:\d+: warning:} # http://rubular.com/r/kqeUIZOfPG
29
- @orig_stderr.write(line)
30
- @output_tracker.write(line)
31
- end
40
+ return if line =~ %r{^\S+/gems/\S+:\d+: warning:} # http://rubular.com/r/kqeUIZOfPG
41
+
42
+ # Ruby 2.7.0 warnings from keyword arguments span multiple lines, extend check above
43
+ # to look for the next line.
44
+ return if @last_line =~ %r{^\S+/gems/\S+:\d+: warning:} &&
45
+ line =~ %r{warning: The called method .* is defined here}
46
+
47
+ # Ruby 2.7.0 complains about hashes used in place of keyword arguments
48
+ # Aruba 0.14.2 uses this internally triggering that here
49
+ return if line =~ %r{lib/ruby/2\.7\.0/fileutils\.rb:622: warning:}
50
+
51
+ @orig_stderr.write(line)
52
+ @output_tracker.write(line)
53
+ ensure
54
+ @last_line = line
32
55
  end
33
56
 
34
57
  def has_output?
@@ -39,15 +62,14 @@ module RSpec
39
62
  @output_tracker = ::StringIO.new
40
63
  end
41
64
 
42
- def verify_example!(example)
43
- example.send(:fail,"Warnings were generated: #{output}") if has_output?
65
+ def verify_no_warnings!
66
+ raise "Warnings were generated: #{output}" if has_output?
44
67
  reset!
45
68
  end
46
69
 
47
70
  def output
48
71
  @output_tracker.string
49
72
  end
50
-
51
73
  end
52
74
  end
53
75
  end
@@ -0,0 +1,45 @@
1
+ require 'rspec/matchers'
2
+ # Special matcher for comparing encoded strings so that
3
+ # we don't run any expectation failures through the Differ,
4
+ # which also relies on EncodedString. Instead, confirm the
5
+ # strings have the same bytes.
6
+ RSpec::Matchers.define :be_identical_string do |expected|
7
+ if String.method_defined?(:encoding)
8
+ match do
9
+ expected_encoding? &&
10
+ actual.bytes.to_a == expected.bytes.to_a
11
+ end
12
+
13
+ failure_message do
14
+ "expected\n#{actual.inspect} (#{actual.encoding.name}) to be identical to\n"\
15
+ "#{expected.inspect} (#{expected.encoding.name})\n"\
16
+ "The exact bytes are printed below for more detail:\n"\
17
+ "#{actual.bytes.to_a}\n"\
18
+ "#{expected.bytes.to_a}\n"\
19
+ end
20
+
21
+ # Depends on chaining :with_same_encoding for it to
22
+ # check for string encoding.
23
+ def expected_encoding?
24
+ if defined?(@expect_same_encoding) && @expect_same_encoding
25
+ actual.encoding == expected.encoding
26
+ else
27
+ true
28
+ end
29
+ end
30
+ else
31
+ match do
32
+ actual.split(//) == expected.split(//)
33
+ end
34
+
35
+ failure_message do
36
+ "expected\n#{actual.inspect} to be identical to\n#{expected.inspect}\n"
37
+ end
38
+ end
39
+
40
+ chain :with_same_encoding do
41
+ @expect_same_encoding ||= true
42
+ end
43
+ end
44
+ RSpec::Matchers.alias_matcher :a_string_identical_to, :be_identical_string
45
+ RSpec::Matchers.alias_matcher :be_diffed_as, :be_identical_string
@@ -0,0 +1,13 @@
1
+ require 'tmpdir'
2
+
3
+ RSpec.shared_context "isolated directory" do
4
+ around do |ex|
5
+ Dir.mktmpdir do |tmp_dir|
6
+ Dir.chdir(tmp_dir, &ex)
7
+ end
8
+ end
9
+ end
10
+
11
+ RSpec.configure do |c|
12
+ c.include_context "isolated directory", :isolated_directory => true
13
+ end
@@ -1,7 +1,6 @@
1
1
  module RSpec
2
2
  module Support
3
3
  module WithIsolatedStdErr
4
-
5
4
  def with_isolated_stderr
6
5
  original = $stderr
7
6
  $stderr = StringIO.new
@@ -9,7 +8,6 @@ module RSpec
9
8
  ensure
10
9
  $stderr = original
11
10
  end
12
-
13
11
  end
14
12
  end
15
13
  end
@@ -1,8 +1,13 @@
1
1
  require 'rspec/support'
2
+ require 'rspec/support/spec/in_sub_process'
3
+
2
4
  RSpec::Support.require_rspec_support "spec/deprecation_helpers"
5
+ RSpec::Support.require_rspec_support "spec/diff_helpers"
3
6
  RSpec::Support.require_rspec_support "spec/with_isolated_stderr"
4
7
  RSpec::Support.require_rspec_support "spec/stderr_splitter"
5
8
  RSpec::Support.require_rspec_support "spec/formatting_support"
9
+ RSpec::Support.require_rspec_support "spec/with_isolated_directory"
10
+ RSpec::Support.require_rspec_support "ruby_features"
6
11
 
7
12
  warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr)
8
13
 
@@ -10,14 +15,15 @@ RSpec.configure do |c|
10
15
  c.include RSpecHelpers
11
16
  c.include RSpec::Support::WithIsolatedStdErr
12
17
  c.include RSpec::Support::FormattingSupport
18
+ c.include RSpec::Support::InSubProcess
13
19
 
14
20
  unless defined?(Debugger) # debugger causes warnings when used
15
21
  c.before do
16
22
  warning_preventer.reset!
17
23
  end
18
24
 
19
- c.after do |example|
20
- warning_preventer.verify_example!(example)
25
+ c.after do
26
+ warning_preventer.verify_no_warnings!
21
27
  end
22
28
  end
23
29
 
@@ -26,37 +32,51 @@ RSpec.configure do |c|
26
32
  c.default_formatter = 'doc'
27
33
  end
28
34
 
29
- c.filter_run :focus
30
- c.run_all_when_everything_filtered = true
31
- end
35
+ c.filter_run_when_matching :focus
32
36
 
33
- module RSpec::Support::Spec
34
- def self.setup_simplecov(&block)
35
- # Simplecov emits some ruby warnings when loaded, so silence them.
36
- old_verbose, $VERBOSE = $VERBOSE, false
37
+ c.example_status_persistence_file_path = "./spec/examples.txt"
38
+
39
+ c.define_derived_metadata :failing_on_windows_ci do |meta|
40
+ meta[:pending] ||= "This spec fails on Windows CI and needs someone to fix it."
41
+ end if RSpec::Support::OS.windows? && ENV['CI']
42
+ end
37
43
 
38
- return if ENV['NO_COVERAGE'] || RUBY_VERSION < '1.9.3' || RUBY_ENGINE != 'ruby'
44
+ module RSpec
45
+ module Support
46
+ module Spec
47
+ def self.setup_simplecov(&block)
48
+ # Simplecov emits some ruby warnings when loaded, so silence them.
49
+ old_verbose, $VERBOSE = $VERBOSE, false
39
50
 
40
- # Don't load it when we're running a single isolated
41
- # test file rather than the whole suite.
42
- return if RSpec.configuration.files_to_run.one?
51
+ return if ENV['NO_COVERAGE'] || RUBY_VERSION < '1.9.3'
52
+ return if RUBY_ENGINE != 'ruby' || RSpec::Support::OS.windows?
43
53
 
44
- require 'simplecov'
54
+ # Don't load it when we're running a single isolated
55
+ # test file rather than the whole suite.
56
+ return if RSpec.configuration.files_to_run.one?
45
57
 
46
- SimpleCov.start do
47
- add_filter "./bundle/"
48
- add_filter "./tmp/"
49
- add_filter do |source_file|
50
- # Filter out `spec` directory except when it is under `lib`
51
- # (as is the case in rspec-support)
52
- source_file.filename.include?('/spec/') && !source_file.filename.include?('/lib/')
58
+ require 'simplecov'
59
+ start_simplecov(&block)
60
+ rescue LoadError
61
+ warn "Simplecov could not be loaded"
62
+ ensure
63
+ $VERBOSE = old_verbose
53
64
  end
54
65
 
55
- instance_eval(&block) if block
66
+ def self.start_simplecov(&block)
67
+ SimpleCov.start do
68
+ add_filter "bundle/"
69
+ add_filter "tmp/"
70
+ add_filter do |source_file|
71
+ # Filter out `spec` directory except when it is under `lib`
72
+ # (as is the case in rspec-support)
73
+ source_file.filename.include?('/spec/') && !source_file.filename.include?('/lib/')
74
+ end
75
+
76
+ instance_eval(&block) if block
77
+ end
78
+ end
79
+ private_class_method :start_simplecov
56
80
  end
57
- rescue LoadError
58
- ensure
59
- $VERBOSE = old_verbose
60
81
  end
61
82
  end
62
-
@@ -1,7 +1,7 @@
1
1
  module RSpec
2
2
  module Support
3
3
  module Version
4
- STRING = '3.0.4'
4
+ STRING = '3.12.1'
5
5
  end
6
6
  end
7
7
  end
@@ -4,7 +4,7 @@ RSpec::Support.require_rspec_support "caller_filter"
4
4
  module RSpec
5
5
  module Support
6
6
  module Warnings
7
- def deprecate(deprecated, options = {})
7
+ def deprecate(deprecated, options={})
8
8
  warn_with "DEPRECATION: #{deprecated} is deprecated.", options
9
9
  end
10
10
 
@@ -12,7 +12,7 @@ module RSpec
12
12
  #
13
13
  # Used internally to print deprecation warnings
14
14
  # when rspec-core isn't loaded
15
- def warn_deprecation(message, options = {})
15
+ def warn_deprecation(message, options={})
16
16
  warn_with "DEPRECATION: \n #{message}", options
17
17
  end
18
18
 
@@ -26,11 +26,11 @@ module RSpec
26
26
  # @private
27
27
  #
28
28
  # Used internally to print longer warnings
29
- def warn_with(message, options = {})
29
+ def warn_with(message, options={})
30
30
  call_site = options.fetch(:call_site) { CallerFilter.first_non_rspec_line }
31
- message << " Use #{options[:replacement]} instead." if options[:replacement]
32
- message << " Called from #{call_site}." if call_site
33
- ::Kernel.warn message
31
+ message += " Use #{options[:replacement]} instead." if options[:replacement]
32
+ message += " Called from #{call_site}." if call_site
33
+ Support.warning_notifier.call message
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,33 @@
1
+ RSpec::Support.require_rspec_support("method_signature_verifier")
2
+
3
+ module RSpec
4
+ module Support
5
+ module WithKeywordsWhenNeeded
6
+ # This module adds keyword sensitive support for core ruby methods
7
+ # where we cannot use `ruby2_keywords` directly.
8
+
9
+ module_function
10
+
11
+ if RSpec::Support::RubyFeatures.kw_args_supported?
12
+ # Remove this in RSpec 4 in favour of explicitly passed in kwargs where
13
+ # this is used. Works around a warning in Ruby 2.7
14
+
15
+ def class_exec(klass, *args, &block)
16
+ if MethodSignature.new(block).has_kw_args_in?(args)
17
+ binding.eval(<<-CODE, __FILE__, __LINE__)
18
+ kwargs = args.pop
19
+ klass.class_exec(*args, **kwargs, &block)
20
+ CODE
21
+ else
22
+ klass.class_exec(*args, &block)
23
+ end
24
+ end
25
+ ruby2_keywords :class_exec if respond_to?(:ruby2_keywords, true)
26
+ else
27
+ def class_exec(klass, *args, &block)
28
+ klass.class_exec(*args, &block)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/rspec/support.rb CHANGED
@@ -14,7 +14,12 @@ module RSpec
14
14
  def self.define_optimized_require_for_rspec(lib, &require_relative)
15
15
  name = "require_rspec_#{lib}"
16
16
 
17
- if Kernel.respond_to?(:require_relative)
17
+ if RUBY_PLATFORM == 'java' && !Kernel.respond_to?(:require)
18
+ # JRuby 9.1.17.0 has developed a regression for require
19
+ (class << self; self; end).__send__(:define_method, name) do |f|
20
+ Kernel.send(:require, "rspec/#{lib}/#{f}")
21
+ end
22
+ elsif Kernel.respond_to?(:require_relative)
18
23
  (class << self; self; end).__send__(:define_method, name) do |f|
19
24
  require_relative.call("#{lib}/#{f}")
20
25
  end
@@ -51,7 +56,7 @@ module RSpec
51
56
  handle = object.method(method_name)
52
57
  raise original unless handle.is_a? Method
53
58
  handle
54
- rescue Exception
59
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue
55
60
  raise original
56
61
  end
57
62
  end
@@ -67,10 +72,89 @@ module RSpec
67
72
  handle = object.method(method_name)
68
73
  raise original unless handle.is_a? Method
69
74
  handle
70
- rescue Exception
75
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue
71
76
  raise original
72
77
  end
73
78
  end
74
79
  end
80
+
81
+ # @api private
82
+ #
83
+ # Used internally to get a class of a given object, even if it does not respond to #class.
84
+ def self.class_of(object)
85
+ object.class
86
+ rescue NoMethodError
87
+ singleton_class = class << object; self; end
88
+ singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) }
89
+ end
90
+
91
+ # A single thread local variable so we don't excessively pollute that namespace.
92
+ if RUBY_VERSION.to_f >= 2
93
+ def self.thread_local_data
94
+ Thread.current.thread_variable_get(:__rspec) || Thread.current.thread_variable_set(:__rspec, {})
95
+ end
96
+ else
97
+ def self.thread_local_data
98
+ Thread.current[:__rspec] ||= {}
99
+ end
100
+ end
101
+
102
+ # @api private
103
+ def self.failure_notifier=(callable)
104
+ thread_local_data[:failure_notifier] = callable
105
+ end
106
+
107
+ # @private
108
+ DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
109
+
110
+ # @api private
111
+ def self.failure_notifier
112
+ thread_local_data[:failure_notifier] || DEFAULT_FAILURE_NOTIFIER
113
+ end
114
+
115
+ # @api private
116
+ def self.notify_failure(failure, options={})
117
+ failure_notifier.call(failure, options)
118
+ end
119
+
120
+ # @api private
121
+ def self.with_failure_notifier(callable)
122
+ orig_notifier = failure_notifier
123
+ self.failure_notifier = callable
124
+ yield
125
+ ensure
126
+ self.failure_notifier = orig_notifier
127
+ end
128
+
129
+ class << self
130
+ # @api private
131
+ attr_writer :warning_notifier
132
+ end
133
+
134
+ # @private
135
+ DEFAULT_WARNING_NOTIFIER = lambda { |warning| ::Kernel.warn warning }
136
+
137
+ # @api private
138
+ def self.warning_notifier
139
+ @warning_notifier ||= DEFAULT_WARNING_NOTIFIER
140
+ end
141
+
142
+ # @private
143
+ module AllExceptionsExceptOnesWeMustNotRescue
144
+ # These exceptions are dangerous to rescue as rescuing them
145
+ # would interfere with things we should not interfere with.
146
+ AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit]
147
+
148
+ def self.===(exception)
149
+ AVOID_RESCUING.none? { |ar| ar === exception }
150
+ end
151
+ end
152
+
153
+ # The Differ is only needed when a spec fails with a diffable failure.
154
+ # In the more common case of all specs passing or the only failures being
155
+ # non-diffable, we can avoid the extra cost of loading the differ, diff-lcs,
156
+ # pp, etc by avoiding an unnecessary require. Instead, autoload will take
157
+ # care of loading the differ on first use.
158
+ autoload :Differ, "rspec/support/differ"
75
159
  end
76
160
  end
data.tar.gz.sig CHANGED
Binary file