sspec-support 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +242 -0
  3. data/LICENSE.md +23 -0
  4. data/README.md +40 -0
  5. data/lib/rspec/support.rb +149 -0
  6. data/lib/rspec/support/caller_filter.rb +83 -0
  7. data/lib/rspec/support/comparable_version.rb +46 -0
  8. data/lib/rspec/support/differ.rb +215 -0
  9. data/lib/rspec/support/directory_maker.rb +63 -0
  10. data/lib/rspec/support/encoded_string.rb +165 -0
  11. data/lib/rspec/support/fuzzy_matcher.rb +48 -0
  12. data/lib/rspec/support/hunk_generator.rb +47 -0
  13. data/lib/rspec/support/matcher_definition.rb +42 -0
  14. data/lib/rspec/support/method_signature_verifier.rb +426 -0
  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 +53 -0
  19. data/lib/rspec/support/ruby_features.rb +176 -0
  20. data/lib/rspec/support/source.rb +75 -0
  21. data/lib/rspec/support/source/location.rb +21 -0
  22. data/lib/rspec/support/source/node.rb +110 -0
  23. data/lib/rspec/support/source/token.rb +87 -0
  24. data/lib/rspec/support/spec.rb +81 -0
  25. data/lib/rspec/support/spec/deprecation_helpers.rb +64 -0
  26. data/lib/rspec/support/spec/formatting_support.rb +9 -0
  27. data/lib/rspec/support/spec/in_sub_process.rb +69 -0
  28. data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
  29. data/lib/rspec/support/spec/shell_out.rb +84 -0
  30. data/lib/rspec/support/spec/stderr_splitter.rb +63 -0
  31. data/lib/rspec/support/spec/string_matcher.rb +46 -0
  32. data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
  33. data/lib/rspec/support/spec/with_isolated_stderr.rb +13 -0
  34. data/lib/rspec/support/version.rb +7 -0
  35. data/lib/rspec/support/warnings.rb +39 -0
  36. metadata +115 -0
@@ -0,0 +1,81 @@
1
+ require 'rspec/support'
2
+ require 'rspec/support/spec/in_sub_process'
3
+
4
+ RSpec::Support.require_rspec_support "spec/deprecation_helpers"
5
+ RSpec::Support.require_rspec_support "spec/with_isolated_stderr"
6
+ RSpec::Support.require_rspec_support "spec/stderr_splitter"
7
+ RSpec::Support.require_rspec_support "spec/formatting_support"
8
+ RSpec::Support.require_rspec_support "spec/with_isolated_directory"
9
+ RSpec::Support.require_rspec_support "ruby_features"
10
+
11
+ warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr)
12
+
13
+ RSpec.configure do |c|
14
+ c.include RSpecHelpers
15
+ c.include RSpec::Support::WithIsolatedStdErr
16
+ c.include RSpec::Support::FormattingSupport
17
+ c.include RSpec::Support::InSubProcess
18
+
19
+ unless defined?(Debugger) # debugger causes warnings when used
20
+ c.before do
21
+ warning_preventer.reset!
22
+ end
23
+
24
+ c.after do
25
+ warning_preventer.verify_no_warnings!
26
+ end
27
+ end
28
+
29
+ if c.files_to_run.one?
30
+ c.full_backtrace = true
31
+ c.default_formatter = 'doc'
32
+ end
33
+
34
+ c.filter_run_when_matching :focus
35
+
36
+ c.example_status_persistence_file_path = "./spec/examples.txt"
37
+
38
+ c.define_derived_metadata :failing_on_appveyor do |meta|
39
+ meta[:pending] ||= "This spec fails on AppVeyor and needs someone to fix it."
40
+ end if ENV['APPVEYOR']
41
+ end
42
+
43
+ module RSpec
44
+ module Support
45
+ module Spec
46
+ def self.setup_simplecov(&block)
47
+ # Simplecov emits some ruby warnings when loaded, so silence them.
48
+ old_verbose, $VERBOSE = $VERBOSE, false
49
+
50
+ return if ENV['NO_COVERAGE'] || RUBY_VERSION < '1.9.3'
51
+ return if RUBY_ENGINE != 'ruby' || RSpec::Support::OS.windows?
52
+
53
+ # Don't load it when we're running a single isolated
54
+ # test file rather than the whole suite.
55
+ return if RSpec.configuration.files_to_run.one?
56
+
57
+ require 'simplecov'
58
+ start_simplecov(&block)
59
+ rescue LoadError
60
+ warn "Simplecov could not be loaded"
61
+ ensure
62
+ $VERBOSE = old_verbose
63
+ end
64
+
65
+ def self.start_simplecov(&block)
66
+ SimpleCov.start do
67
+ add_filter "./bundle/"
68
+ add_filter "./tmp/"
69
+ add_filter do |source_file|
70
+ # Filter out `spec` directory except when it is under `lib`
71
+ # (as is the case in rspec-support)
72
+ source_file.filename.include?('/spec/') && !source_file.filename.include?('/lib/')
73
+ end
74
+
75
+ instance_eval(&block) if block
76
+ end
77
+ end
78
+ private_class_method :start_simplecov
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,64 @@
1
+ module RSpecHelpers
2
+ def expect_no_deprecation
3
+ expect(RSpec.configuration.reporter).not_to receive(:deprecation)
4
+ end
5
+
6
+ def expect_deprecation_with_call_site(file, line, snippet=//)
7
+ expect(RSpec.configuration.reporter).to receive(:deprecation) do |options|
8
+ expect(options[:call_site]).to include([file, line].join(':'))
9
+ expect(options[:deprecated]).to match(snippet)
10
+ end
11
+ end
12
+
13
+ def expect_deprecation_without_call_site(snippet=//)
14
+ expect(RSpec.configuration.reporter).to receive(:deprecation) do |options|
15
+ expect(options[:call_site]).to eq nil
16
+ expect(options[:deprecated]).to match(snippet)
17
+ end
18
+ end
19
+
20
+ def expect_warn_deprecation_with_call_site(file, line, snippet=//)
21
+ expect(RSpec.configuration.reporter).to receive(:deprecation) do |options|
22
+ message = options[:message]
23
+ expect(message).to match(snippet)
24
+ expect(message).to include([file, line].join(':'))
25
+ end
26
+ end
27
+
28
+ def expect_warn_deprecation(snippet=//)
29
+ expect(RSpec.configuration.reporter).to receive(:deprecation) do |options|
30
+ message = options[:message]
31
+ expect(message).to match(snippet)
32
+ end
33
+ end
34
+
35
+ def allow_deprecation
36
+ allow(RSpec.configuration.reporter).to receive(:deprecation)
37
+ end
38
+
39
+ def expect_no_deprecations
40
+ expect(RSpec.configuration.reporter).not_to receive(:deprecation)
41
+ end
42
+
43
+ def expect_warning_without_call_site(expected=//)
44
+ expect(::Kernel).to receive(:warn) do |message|
45
+ expect(message).to match expected
46
+ expect(message).to_not match(/Called from/)
47
+ end
48
+ end
49
+
50
+ def expect_warning_with_call_site(file, line, expected=//)
51
+ expect(::Kernel).to receive(:warn) do |message|
52
+ expect(message).to match expected
53
+ expect(message).to match(/Called from #{file}:#{line}/)
54
+ end
55
+ end
56
+
57
+ def expect_no_warnings
58
+ expect(::Kernel).not_to receive(:warn)
59
+ end
60
+
61
+ def allow_warning
62
+ allow(::Kernel).to receive(:warn)
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ module RSpec
2
+ module Support
3
+ module FormattingSupport
4
+ def dedent(string)
5
+ string.gsub(/^\s+\|/, '').chomp
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,69 @@
1
+ module RSpec
2
+ module Support
3
+ module InSubProcess
4
+ if Process.respond_to?(:fork) && !(Ruby.jruby? && RUBY_VERSION == '1.8.7')
5
+
6
+ UnmarshableObject = Struct.new(:error)
7
+
8
+ # Useful as a way to isolate a global change to a subprocess.
9
+
10
+ # rubocop:disable MethodLength
11
+ def in_sub_process(prevent_warnings=true)
12
+ exception_reader, exception_writer = IO.pipe
13
+ result_reader, result_writer = IO.pipe
14
+
15
+ pid = Process.fork do
16
+ warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr)
17
+
18
+ begin
19
+ result = yield
20
+ warning_preventer.verify_no_warnings! if prevent_warnings
21
+ # rubocop:disable Lint/HandleExceptions
22
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception
23
+ # rubocop:enable Lint/HandleExceptions
24
+ end
25
+
26
+ exception_writer.write marshal_dump_with_unmarshable_object_handling(exception)
27
+ exception_reader.close
28
+ exception_writer.close
29
+
30
+ result_writer.write marshal_dump_with_unmarshable_object_handling(result)
31
+ result_reader.close
32
+ result_writer.close
33
+
34
+ exit! # prevent at_exit hooks from running (e.g. minitest)
35
+ end
36
+
37
+ exception_writer.close
38
+ result_writer.close
39
+ Process.waitpid(pid)
40
+
41
+ exception = Marshal.load(exception_reader.read)
42
+ exception_reader.close
43
+ raise exception if exception
44
+
45
+ result = Marshal.load(result_reader.read)
46
+ result_reader.close
47
+ result
48
+ end
49
+ # rubocop:enable MethodLength
50
+ alias :in_sub_process_if_possible :in_sub_process
51
+
52
+ def marshal_dump_with_unmarshable_object_handling(object)
53
+ Marshal.dump(object)
54
+ rescue TypeError => error
55
+ Marshal.dump(UnmarshableObject.new(error))
56
+ end
57
+ else
58
+ def in_sub_process(*)
59
+ skip "This spec requires forking to work properly, " \
60
+ "and your platform does not support forking"
61
+ end
62
+
63
+ def in_sub_process_if_possible(*)
64
+ yield
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,150 @@
1
+ require 'rspec/support/spec/shell_out'
2
+
3
+ module RSpec
4
+ module Support
5
+ module WhitespaceChecks
6
+ # This malformed whitespace detection logic has been borrowed from bundler:
7
+ # https://github.com/bundler/bundler/blob/v1.8.0/spec/quality_spec.rb
8
+ def check_for_tab_characters(filename)
9
+ failing_lines = []
10
+ File.readlines(filename).each_with_index do |line, number|
11
+ failing_lines << number + 1 if line =~ /\t/
12
+ end
13
+
14
+ return if failing_lines.empty?
15
+ "#{filename} has tab characters on lines #{failing_lines.join(', ')}"
16
+ end
17
+
18
+ def check_for_extra_spaces(filename)
19
+ failing_lines = []
20
+ File.readlines(filename).each_with_index do |line, number|
21
+ next if line =~ /^\s+#.*\s+\n$/
22
+ failing_lines << number + 1 if line =~ /\s+\n$/
23
+ end
24
+
25
+ return if failing_lines.empty?
26
+ "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ RSpec.shared_examples_for "library wide checks" do |lib, options|
33
+ consider_a_test_env_file = options.fetch(:consider_a_test_env_file, /MATCHES NOTHING/)
34
+ allowed_loaded_feature_regexps = options.fetch(:allowed_loaded_feature_regexps, [])
35
+ preamble_for_lib = options[:preamble_for_lib]
36
+ preamble_for_spec = "require 'rspec/core'; require 'spec_helper'"
37
+ skip_spec_files = options.fetch(:skip_spec_files, /MATCHES NOTHING/)
38
+
39
+ include RSpec::Support::ShellOut
40
+ include RSpec::Support::WhitespaceChecks
41
+
42
+ define_method :files_to_require_for do |sub_dir|
43
+ slash = File::SEPARATOR
44
+ lib_path_re = /#{slash + lib}[^#{slash}]*#{slash}lib/
45
+ load_path = $LOAD_PATH.grep(lib_path_re).first
46
+ directory = load_path.sub(/lib$/, sub_dir)
47
+ files = Dir["#{directory}/**/*.rb"]
48
+ extract_regex = /#{Regexp.escape(directory) + File::SEPARATOR}(.+)\.rb$/
49
+
50
+ # We sort to ensure the files are loaded in a consistent order, regardless
51
+ # of OS. Otherwise, it could load in a different order on Travis than
52
+ # locally, and potentially trigger a "circular require considered harmful"
53
+ # warning or similar.
54
+ files.sort.map { |file| file[extract_regex, 1] }
55
+ end
56
+
57
+ def command_from(code_lines)
58
+ code_lines.join("\n")
59
+ end
60
+
61
+ def load_all_files(files, preamble, postamble=nil)
62
+ requires = files.map { |f| "require '#{f}'" }
63
+ command = command_from(Array(preamble) + requires + Array(postamble))
64
+
65
+ stdout, stderr, status = with_env 'NO_COVERAGE' => '1' do
66
+ options = %w[ -w ]
67
+ options << "--disable=gem" if RUBY_VERSION.to_f >= 1.9 && RSpec::Support::Ruby.mri?
68
+ run_ruby_with_current_load_path(command, *options)
69
+ end
70
+
71
+ [stdout, strip_known_warnings(stderr), status.exitstatus]
72
+ end
73
+
74
+ define_method :load_all_lib_files do
75
+ files = all_lib_files - lib_test_env_files
76
+ preamble = ['orig_loaded_features = $".dup', preamble_for_lib]
77
+ postamble = ['puts(($" - orig_loaded_features).join("\n"))']
78
+
79
+ @loaded_feature_lines, stderr, exitstatus = load_all_files(files, preamble, postamble)
80
+ ["", stderr, exitstatus]
81
+ end
82
+
83
+ define_method :load_all_spec_files do
84
+ files = files_to_require_for("spec") + lib_test_env_files
85
+ files = files.reject { |f| f =~ skip_spec_files }
86
+ load_all_files(files, preamble_for_spec)
87
+ end
88
+
89
+ attr_reader :all_lib_files, :lib_test_env_files,
90
+ :lib_file_results, :spec_file_results
91
+
92
+ before(:context) do
93
+ @all_lib_files = files_to_require_for("lib")
94
+ @lib_test_env_files = all_lib_files.grep(consider_a_test_env_file)
95
+
96
+ @lib_file_results, @spec_file_results = [
97
+ # Load them in parallel so it's faster...
98
+ Thread.new { load_all_lib_files },
99
+ Thread.new { load_all_spec_files }
100
+ ].map(&:join).map(&:value)
101
+ end
102
+
103
+ def have_successful_no_warnings_output
104
+ eq ["", "", 0]
105
+ end
106
+
107
+ it "issues no warnings when loaded", :slow do
108
+ expect(lib_file_results).to have_successful_no_warnings_output
109
+ end
110
+
111
+ it "issues no warnings when the spec files are loaded", :slow do
112
+ expect(spec_file_results).to have_successful_no_warnings_output
113
+ end
114
+
115
+ it 'only loads a known set of stdlibs so gem authors are forced ' \
116
+ 'to load libs they use to have passing specs', :slow do
117
+ loaded_features = @loaded_feature_lines.split("\n")
118
+ if RUBY_VERSION == '1.8.7'
119
+ # On 1.8.7, $" returns the relative require path if that was used
120
+ # to require the file. LIB_REGEX will not match the relative version
121
+ # since it has a `/lib` prefix. Here we deal with this by expanding
122
+ # relative files relative to the $LOAD_PATH dir (lib).
123
+ Dir.chdir("lib") { loaded_features.map! { |f| File.expand_path(f) } }
124
+ end
125
+
126
+ loaded_features.reject! { |feature| RSpec::CallerFilter::LIB_REGEX =~ feature }
127
+ loaded_features.reject! { |feature| allowed_loaded_feature_regexps.any? { |r| r =~ feature } }
128
+
129
+ expect(loaded_features).to eq([])
130
+ end
131
+
132
+ RSpec::Matchers.define :be_well_formed do
133
+ match do |actual|
134
+ actual.empty?
135
+ end
136
+
137
+ failure_message do |actual|
138
+ actual.join("\n")
139
+ end
140
+ end
141
+
142
+ it "has no malformed whitespace", :slow do
143
+ error_messages = []
144
+ `git ls-files -z`.split("\x0").each do |filename|
145
+ error_messages << check_for_tab_characters(filename)
146
+ error_messages << check_for_extra_spaces(filename)
147
+ end
148
+ expect(error_messages.compact).to be_well_formed
149
+ end
150
+ end
@@ -0,0 +1,84 @@
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
+ def strip_known_warnings(input)
57
+ input.split("\n").reject do |l|
58
+ # Ignore bundler warning.
59
+ l =~ %r{bundler/source/rubygems} ||
60
+ # Ignore bundler + rubygems warning.
61
+ l =~ %r{site_ruby/\d\.\d\.\d/rubygems} ||
62
+ # This is required for windows for some reason
63
+ l =~ %r{lib/bundler/rubygems} ||
64
+ # This is a JRuby file that generates warnings on 9.0.3.0
65
+ l =~ %r{lib/ruby/stdlib/jar}
66
+ end.join("\n")
67
+ end
68
+
69
+ private
70
+
71
+ if Ruby.jruby?
72
+ def filter(output)
73
+ output.each_line.reject do |line|
74
+ line.include?("lib/ruby/shared/rubygems/defaults/jruby")
75
+ end.join($/)
76
+ end
77
+ else
78
+ def filter(output)
79
+ output
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end