sspec-support 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changelog.md +242 -0
- data/LICENSE.md +23 -0
- data/README.md +40 -0
- data/lib/rspec/support.rb +149 -0
- data/lib/rspec/support/caller_filter.rb +83 -0
- data/lib/rspec/support/comparable_version.rb +46 -0
- data/lib/rspec/support/differ.rb +215 -0
- data/lib/rspec/support/directory_maker.rb +63 -0
- data/lib/rspec/support/encoded_string.rb +165 -0
- data/lib/rspec/support/fuzzy_matcher.rb +48 -0
- data/lib/rspec/support/hunk_generator.rb +47 -0
- data/lib/rspec/support/matcher_definition.rb +42 -0
- data/lib/rspec/support/method_signature_verifier.rb +426 -0
- data/lib/rspec/support/mutex.rb +73 -0
- data/lib/rspec/support/object_formatter.rb +275 -0
- data/lib/rspec/support/recursive_const_methods.rb +76 -0
- data/lib/rspec/support/reentrant_mutex.rb +53 -0
- data/lib/rspec/support/ruby_features.rb +176 -0
- data/lib/rspec/support/source.rb +75 -0
- data/lib/rspec/support/source/location.rb +21 -0
- data/lib/rspec/support/source/node.rb +110 -0
- data/lib/rspec/support/source/token.rb +87 -0
- data/lib/rspec/support/spec.rb +81 -0
- data/lib/rspec/support/spec/deprecation_helpers.rb +64 -0
- data/lib/rspec/support/spec/formatting_support.rb +9 -0
- data/lib/rspec/support/spec/in_sub_process.rb +69 -0
- data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
- data/lib/rspec/support/spec/shell_out.rb +84 -0
- data/lib/rspec/support/spec/stderr_splitter.rb +63 -0
- data/lib/rspec/support/spec/string_matcher.rb +46 -0
- data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
- data/lib/rspec/support/spec/with_isolated_stderr.rb +13 -0
- data/lib/rspec/support/version.rb +7 -0
- data/lib/rspec/support/warnings.rb +39 -0
- 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,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
|