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.
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,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) do |options|
9
- expect(options[:call_site]).to include([file, line].join(':'))
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) do |options|
16
- expect(options[:call_site]).to eq nil
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) do |options|
23
- message = options[: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) do |options|
31
- message = options[: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 expect_warning_without_call_site(expected = //)
45
- expect(::Kernel).to receive(:warn) do |message|
46
- expect(message).to match expected
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 expect_warning_with_call_site(file, line, expected = //)
52
- expect(::Kernel).to receive(:warn) do |message|
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) && !(RUBY_PLATFORM == 'java' && RUBY_VERSION == '1.8.7')
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
- def in_sub_process
7
- readme, writeme = IO.pipe
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
- exception = nil
15
+ warning_preventer = $stderr = RSpec::Support::StdErrSplitter.new($stderr)
16
+
11
17
  begin
12
- yield
13
- rescue Exception => e
14
- exception = e
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
- writeme.write Marshal.dump(exception)
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
- writeme.close
36
+ exception_writer.close
37
+ result_writer.close
25
38
  Process.waitpid(pid)
26
39
 
27
- exception = Marshal.load(readme.read)
28
- readme.close
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