rspec-support 3.0.4 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/Changelog.md +322 -0
- data/{LICENSE.txt → LICENSE.md} +3 -2
- data/README.md +29 -6
- data/lib/rspec/support/caller_filter.rb +35 -16
- data/lib/rspec/support/comparable_version.rb +46 -0
- data/lib/rspec/support/differ.rb +51 -41
- data/lib/rspec/support/directory_maker.rb +63 -0
- data/lib/rspec/support/encoded_string.rb +110 -15
- data/lib/rspec/support/fuzzy_matcher.rb +5 -6
- data/lib/rspec/support/hunk_generator.rb +0 -1
- data/lib/rspec/support/matcher_definition.rb +42 -0
- data/lib/rspec/support/method_signature_verifier.rb +287 -54
- 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 +78 -0
- data/lib/rspec/support/ruby_features.rb +177 -14
- 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 +94 -0
- data/lib/rspec/support/source.rb +85 -0
- data/lib/rspec/support/spec/deprecation_helpers.rb +19 -32
- data/lib/rspec/support/spec/diff_helpers.rb +31 -0
- data/lib/rspec/support/spec/in_sub_process.rb +43 -16
- data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
- data/lib/rspec/support/spec/shell_out.rb +108 -0
- data/lib/rspec/support/spec/stderr_splitter.rb +31 -9
- data/lib/rspec/support/spec/string_matcher.rb +45 -0
- data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
- data/lib/rspec/support/spec/with_isolated_stderr.rb +0 -2
- data/lib/rspec/support/spec.rb +46 -26
- data/lib/rspec/support/version.rb +1 -1
- data/lib/rspec/support/warnings.rb +6 -6
- data/lib/rspec/support/with_keywords_when_needed.rb +33 -0
- data/lib/rspec/support.rb +87 -3
- data.tar.gz.sig +0 -0
- metadata +70 -52
- metadata.gz.sig +0 -0
- data/lib/rspec/support/version_checker.rb +0 -53
@@ -0,0 +1,110 @@
|
|
1
|
+
RSpec::Support.require_rspec_support 'source/location'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Support
|
5
|
+
class Source
|
6
|
+
# @private
|
7
|
+
# A wrapper for Ripper AST node which is generated with `Ripper.sexp`.
|
8
|
+
class Node
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
attr_reader :sexp, :parent
|
12
|
+
|
13
|
+
def self.sexp?(array)
|
14
|
+
array.is_a?(Array) && array.first.is_a?(Symbol)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(ripper_sexp, parent=nil)
|
18
|
+
@sexp = ripper_sexp.freeze
|
19
|
+
@parent = parent
|
20
|
+
end
|
21
|
+
|
22
|
+
def type
|
23
|
+
sexp[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
def args
|
27
|
+
@args ||= raw_args.map do |raw_arg|
|
28
|
+
if Node.sexp?(raw_arg)
|
29
|
+
Node.new(raw_arg, self)
|
30
|
+
elsif Location.location?(raw_arg)
|
31
|
+
Location.new(*raw_arg)
|
32
|
+
elsif raw_arg.is_a?(Array)
|
33
|
+
ExpressionSequenceNode.new(raw_arg, self)
|
34
|
+
else
|
35
|
+
raw_arg
|
36
|
+
end
|
37
|
+
end.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def children
|
41
|
+
@children ||= args.select { |arg| arg.is_a?(Node) }.freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
def location
|
45
|
+
@location ||= args.find { |arg| arg.is_a?(Location) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# We use a loop here (instead of recursion) to prevent SystemStackError
|
49
|
+
def each
|
50
|
+
return to_enum(__method__) unless block_given?
|
51
|
+
|
52
|
+
node_queue = []
|
53
|
+
node_queue << self
|
54
|
+
|
55
|
+
while (current_node = node_queue.shift)
|
56
|
+
yield current_node
|
57
|
+
node_queue.concat(current_node.children)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def each_ancestor
|
62
|
+
return to_enum(__method__) unless block_given?
|
63
|
+
|
64
|
+
current_node = self
|
65
|
+
|
66
|
+
while (current_node = current_node.parent)
|
67
|
+
yield current_node
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
"#<#{self.class} #{type}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def raw_args
|
78
|
+
sexp[1..-1] || []
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @private
|
83
|
+
# Basically `Ripper.sexp` generates arrays whose first element is a symbol (type of sexp),
|
84
|
+
# but it exceptionally generates typeless arrays for expression sequence:
|
85
|
+
#
|
86
|
+
# Ripper.sexp('foo; bar')
|
87
|
+
# => [
|
88
|
+
# :program,
|
89
|
+
# [ # Typeless array
|
90
|
+
# [:vcall, [:@ident, "foo", [1, 0]]],
|
91
|
+
# [:vcall, [:@ident, "bar", [1, 5]]]
|
92
|
+
# ]
|
93
|
+
# ]
|
94
|
+
#
|
95
|
+
# We wrap typeless arrays in this pseudo type node
|
96
|
+
# so that it can be handled in the same way as other type node.
|
97
|
+
class ExpressionSequenceNode < Node
|
98
|
+
def type
|
99
|
+
:_expression_sequence
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def raw_args
|
105
|
+
sexp
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
RSpec::Support.require_rspec_support 'source/location'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Support
|
5
|
+
class Source
|
6
|
+
# @private
|
7
|
+
# A wrapper for Ripper token which is generated with `Ripper.lex`.
|
8
|
+
class Token
|
9
|
+
CLOSING_TYPES_BY_OPENING_TYPE = {
|
10
|
+
:on_lbracket => :on_rbracket,
|
11
|
+
:on_lparen => :on_rparen,
|
12
|
+
:on_lbrace => :on_rbrace,
|
13
|
+
:on_heredoc_beg => :on_heredoc_end
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
CLOSING_KEYWORDS_BY_OPENING_KEYWORD = {
|
17
|
+
'def' => 'end',
|
18
|
+
'do' => 'end',
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
attr_reader :token
|
22
|
+
|
23
|
+
def self.tokens_from_ripper_tokens(ripper_tokens)
|
24
|
+
ripper_tokens.map { |ripper_token| new(ripper_token) }.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(ripper_token)
|
28
|
+
@token = ripper_token.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
def location
|
32
|
+
@location ||= Location.new(*token[0])
|
33
|
+
end
|
34
|
+
|
35
|
+
def type
|
36
|
+
token[1]
|
37
|
+
end
|
38
|
+
|
39
|
+
def string
|
40
|
+
token[2]
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
token == other.token
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :eql?, :==
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
"#<#{self.class} #{type} #{string.inspect}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
def keyword?
|
54
|
+
type == :on_kw
|
55
|
+
end
|
56
|
+
|
57
|
+
def equals_operator?
|
58
|
+
type == :on_op && string == '='
|
59
|
+
end
|
60
|
+
|
61
|
+
def opening?
|
62
|
+
opening_delimiter? || opening_keyword?
|
63
|
+
end
|
64
|
+
|
65
|
+
def closed_by?(other)
|
66
|
+
delimiter_closed_by?(other) || keyword_closed_by?(other)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def opening_delimiter?
|
72
|
+
CLOSING_TYPES_BY_OPENING_TYPE.key?(type)
|
73
|
+
end
|
74
|
+
|
75
|
+
def opening_keyword?
|
76
|
+
return false unless keyword?
|
77
|
+
CLOSING_KEYWORDS_BY_OPENING_KEYWORD.key?(string)
|
78
|
+
end
|
79
|
+
|
80
|
+
def delimiter_closed_by?(other)
|
81
|
+
other.type == CLOSING_TYPES_BY_OPENING_TYPE[type]
|
82
|
+
end
|
83
|
+
|
84
|
+
def keyword_closed_by?(other)
|
85
|
+
return false unless keyword?
|
86
|
+
return true if other.string == CLOSING_KEYWORDS_BY_OPENING_KEYWORD[string]
|
87
|
+
|
88
|
+
# Ruby 3's `end`-less method definition: `def method_name = body`
|
89
|
+
string == 'def' && other.equals_operator? && location.line == other.location.line
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
RSpec::Support.require_rspec_support 'encoded_string'
|
2
|
+
RSpec::Support.require_rspec_support 'ruby_features'
|
3
|
+
|
4
|
+
module RSpec
|
5
|
+
module Support
|
6
|
+
# @private
|
7
|
+
# Represents a Ruby source file and provides access to AST and tokens.
|
8
|
+
class Source
|
9
|
+
attr_reader :source, :path
|
10
|
+
|
11
|
+
# This class protects us against having File read and expand_path
|
12
|
+
# stubbed out within tests.
|
13
|
+
class File
|
14
|
+
class << self
|
15
|
+
[:read, :expand_path].each do |method_name|
|
16
|
+
define_method(method_name, &::File.method(method_name))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.from_file(path)
|
22
|
+
source = File.read(path)
|
23
|
+
new(source, path)
|
24
|
+
end
|
25
|
+
|
26
|
+
if String.method_defined?(:encoding)
|
27
|
+
def initialize(source_string, path=nil)
|
28
|
+
@source = RSpec::Support::EncodedString.new(source_string, Encoding.default_external)
|
29
|
+
@path = path ? File.expand_path(path) : '(string)'
|
30
|
+
end
|
31
|
+
else # for 1.8.7
|
32
|
+
# :nocov:
|
33
|
+
def initialize(source_string, path=nil)
|
34
|
+
@source = RSpec::Support::EncodedString.new(source_string)
|
35
|
+
@path = path ? File.expand_path(path) : '(string)'
|
36
|
+
end
|
37
|
+
# :nocov:
|
38
|
+
end
|
39
|
+
|
40
|
+
def lines
|
41
|
+
@lines ||= source.split("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"#<#{self.class} #{path}>"
|
46
|
+
end
|
47
|
+
|
48
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
49
|
+
RSpec::Support.require_rspec_support 'source/node'
|
50
|
+
RSpec::Support.require_rspec_support 'source/token'
|
51
|
+
|
52
|
+
def ast
|
53
|
+
@ast ||= begin
|
54
|
+
require 'ripper'
|
55
|
+
sexp = Ripper.sexp(source)
|
56
|
+
raise SyntaxError unless sexp
|
57
|
+
Node.new(sexp)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def tokens
|
62
|
+
@tokens ||= begin
|
63
|
+
require 'ripper'
|
64
|
+
tokens = Ripper.lex(source)
|
65
|
+
Token.tokens_from_ripper_tokens(tokens)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def nodes_by_line_number
|
70
|
+
@nodes_by_line_number ||= begin
|
71
|
+
nodes_by_line_number = ast.select(&:location).group_by { |node| node.location.line }
|
72
|
+
Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def tokens_by_line_number
|
77
|
+
@tokens_by_line_number ||= begin
|
78
|
+
nodes_by_line_number = tokens.group_by { |token| token.location.line }
|
79
|
+
Hash.new { |hash, key| hash[key] = [] }.merge(nodes_by_line_number)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -1,36 +1,22 @@
|
|
1
1
|
module RSpecHelpers
|
2
|
-
|
3
|
-
def expect_no_deprecation
|
4
|
-
expect(RSpec.configuration.reporter).not_to receive(:deprecation)
|
5
|
-
end
|
6
|
-
|
7
2
|
def expect_deprecation_with_call_site(file, line, snippet=//)
|
8
|
-
expect(RSpec.configuration.reporter).to receive(:deprecation)
|
9
|
-
|
10
|
-
expect(options[:deprecated]).to match(snippet)
|
11
|
-
end
|
3
|
+
expect(RSpec.configuration.reporter).to receive(:deprecation).
|
4
|
+
with(include(:deprecated => match(snippet), :call_site => include([file, line].join(':'))))
|
12
5
|
end
|
13
6
|
|
14
7
|
def expect_deprecation_without_call_site(snippet=//)
|
15
|
-
expect(RSpec.configuration.reporter).to receive(:deprecation)
|
16
|
-
|
17
|
-
expect(options[:deprecated]).to match(snippet)
|
18
|
-
end
|
8
|
+
expect(RSpec.configuration.reporter).to receive(:deprecation).
|
9
|
+
with(include(:deprecated => match(snippet), :call_site => eq(nil)))
|
19
10
|
end
|
20
11
|
|
21
12
|
def expect_warn_deprecation_with_call_site(file, line, snippet=//)
|
22
|
-
expect(RSpec.configuration.reporter).to receive(:deprecation)
|
23
|
-
message
|
24
|
-
expect(message).to match(snippet)
|
25
|
-
expect(message).to include([file, line].join(':'))
|
26
|
-
end
|
13
|
+
expect(RSpec.configuration.reporter).to receive(:deprecation).
|
14
|
+
with(include(:message => match(snippet), :call_site => include([file, line].join(':'))))
|
27
15
|
end
|
28
16
|
|
29
17
|
def expect_warn_deprecation(snippet=//)
|
30
|
-
expect(RSpec.configuration.reporter).to receive(:deprecation)
|
31
|
-
message
|
32
|
-
expect(message).to match(snippet)
|
33
|
-
end
|
18
|
+
expect(RSpec.configuration.reporter).to receive(:deprecation).
|
19
|
+
with(include(:message => match(snippet)))
|
34
20
|
end
|
35
21
|
|
36
22
|
def allow_deprecation
|
@@ -40,19 +26,20 @@ module RSpecHelpers
|
|
40
26
|
def expect_no_deprecations
|
41
27
|
expect(RSpec.configuration.reporter).not_to receive(:deprecation)
|
42
28
|
end
|
29
|
+
alias expect_no_deprecation expect_no_deprecations
|
30
|
+
|
31
|
+
def expect_warning_without_call_site(expected=//)
|
32
|
+
expect(::Kernel).to receive(:warn).
|
33
|
+
with(match(expected).and(satisfy { |message| !(/Called from/ =~ message) }))
|
34
|
+
end
|
43
35
|
|
44
|
-
def
|
45
|
-
expect(::Kernel).to receive(:warn)
|
46
|
-
|
47
|
-
expect(message).to_not match(/Called from/)
|
48
|
-
end
|
36
|
+
def expect_warning_with_call_site(file, line, expected=//)
|
37
|
+
expect(::Kernel).to receive(:warn).
|
38
|
+
with(match(expected).and(match(/Called from #{file}:#{line}/)))
|
49
39
|
end
|
50
40
|
|
51
|
-
def
|
52
|
-
expect(::Kernel).
|
53
|
-
expect(message).to match expected
|
54
|
-
expect(message).to match(/Called from #{file}:#{line}/)
|
55
|
-
end
|
41
|
+
def expect_no_warnings
|
42
|
+
expect(::Kernel).not_to receive(:warn)
|
56
43
|
end
|
57
44
|
|
58
45
|
def allow_warning
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'diff/lcs'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Support
|
5
|
+
module Spec
|
6
|
+
module DiffHelpers
|
7
|
+
# In the updated version of diff-lcs several diff headers change format slightly
|
8
|
+
# compensate for this and change minimum version in RSpec 4
|
9
|
+
if ::Diff::LCS::VERSION.to_f < 1.4
|
10
|
+
def one_line_header(line_number=2)
|
11
|
+
"-1,#{line_number} +1,#{line_number}"
|
12
|
+
end
|
13
|
+
else
|
14
|
+
def one_line_header(_=2)
|
15
|
+
"-1 +1"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if Diff::LCS::VERSION.to_f < 1.4 || Diff::LCS::VERSION >= "1.4.4"
|
20
|
+
def removing_two_line_header
|
21
|
+
"-1,3 +1"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
def removing_two_line_header
|
25
|
+
"-1,3 +1,5"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,39 +1,66 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Support
|
3
3
|
module InSubProcess
|
4
|
-
if Process.respond_to?(:fork) && !(
|
4
|
+
if Process.respond_to?(:fork) && !(Ruby.jruby? && RUBY_VERSION == '1.8.7')
|
5
|
+
|
6
|
+
UnmarshableObject = Struct.new(:error)
|
7
|
+
|
5
8
|
# Useful as a way to isolate a global change to a subprocess.
|
6
|
-
|
7
|
-
|
9
|
+
|
10
|
+
def in_sub_process(prevent_warnings=true) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
11
|
+
exception_reader, exception_writer = IO.pipe
|
12
|
+
result_reader, result_writer = IO.pipe
|
8
13
|
|
9
14
|
pid = Process.fork do
|
10
|
-
|
15
|
+
warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr)
|
16
|
+
|
11
17
|
begin
|
12
|
-
yield
|
13
|
-
|
14
|
-
|
18
|
+
result = yield
|
19
|
+
warning_preventer.verify_no_warnings! if prevent_warnings
|
20
|
+
# rubocop:disable Lint/HandleExceptions
|
21
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => exception
|
22
|
+
# rubocop:enable Lint/HandleExceptions
|
15
23
|
end
|
16
24
|
|
17
|
-
|
25
|
+
exception_writer.write marshal_dump_with_unmarshable_object_handling(exception)
|
26
|
+
exception_reader.close
|
27
|
+
exception_writer.close
|
28
|
+
|
29
|
+
result_writer.write marshal_dump_with_unmarshable_object_handling(result)
|
30
|
+
result_reader.close
|
31
|
+
result_writer.close
|
18
32
|
|
19
|
-
readme.close
|
20
|
-
writeme.close
|
21
33
|
exit! # prevent at_exit hooks from running (e.g. minitest)
|
22
34
|
end
|
23
35
|
|
24
|
-
|
36
|
+
exception_writer.close
|
37
|
+
result_writer.close
|
25
38
|
Process.waitpid(pid)
|
26
39
|
|
27
|
-
exception = Marshal.load(
|
28
|
-
|
29
|
-
|
40
|
+
exception = Marshal.load(exception_reader.read)
|
41
|
+
exception_reader.close
|
30
42
|
raise exception if exception
|
43
|
+
|
44
|
+
result = Marshal.load(result_reader.read)
|
45
|
+
result_reader.close
|
46
|
+
result
|
47
|
+
end
|
48
|
+
alias :in_sub_process_if_possible :in_sub_process
|
49
|
+
|
50
|
+
def marshal_dump_with_unmarshable_object_handling(object)
|
51
|
+
Marshal.dump(object)
|
52
|
+
rescue TypeError => error
|
53
|
+
Marshal.dump(UnmarshableObject.new(error))
|
31
54
|
end
|
32
55
|
else
|
33
|
-
def in_sub_process
|
34
|
-
skip "This spec requires forking to work properly, "
|
56
|
+
def in_sub_process(*)
|
57
|
+
skip "This spec requires forking to work properly, " \
|
35
58
|
"and your platform does not support forking"
|
36
59
|
end
|
60
|
+
|
61
|
+
def in_sub_process_if_possible(*)
|
62
|
+
yield
|
63
|
+
end
|
37
64
|
end
|
38
65
|
end
|
39
66
|
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
|