|
| 1 | +require 'rspec/support/spec/shell_out' |
| 2 | +require 'tempfile' |
| 3 | + |
| 4 | +RSpec.shared_examples_for "library wide checks" do |lib, options| |
| 5 | + consider_a_test_env_file = options.fetch(:consider_a_test_env_file, /MATCHES NOTHING/) |
| 6 | + allowed_loaded_feature_regexps = options.fetch(:allowed_loaded_feature_regexps, []) |
| 7 | + preamble_for_lib = options[:preamble_for_lib] |
| 8 | + preamble_for_spec = "require 'rspec/core'; require 'spec_helper'" |
| 9 | + |
| 10 | + include RSpec::Support::ShellOut |
| 11 | + |
| 12 | + define_method :files_to_require_for do |sub_dir| |
| 13 | + slash = File::SEPARATOR |
| 14 | + lib_path_re = /#{slash + lib}[^#{slash}]*#{slash}lib/ |
| 15 | + load_path = $LOAD_PATH.grep(lib_path_re).first |
| 16 | + directory = load_path.sub(/lib$/, sub_dir) |
| 17 | + files = Dir["#{directory}/**/*.rb"] |
| 18 | + extract_regex = /#{Regexp.escape(directory) + File::SEPARATOR}(.+)\.rb$/ |
| 19 | + |
| 20 | + # We sort to ensure the files are loaded in a consistent order, regardless |
| 21 | + # of OS. Otherwise, it could load in a different order on Travis than |
| 22 | + # locally, and potentially trigger a "circular require considered harmful" |
| 23 | + # warning or similar. |
| 24 | + files.sort.map { |file| file[extract_regex, 1] } |
| 25 | + end |
| 26 | + |
| 27 | + def command_from(code_lines) |
| 28 | + code_lines.join("\n") |
| 29 | + end |
| 30 | + |
| 31 | + def load_all_files(files, preamble, postamble=nil) |
| 32 | + requires = files.map { |f| "require '#{f}'" } |
| 33 | + command = command_from(Array(preamble) + requires + Array(postamble)) |
| 34 | + |
| 35 | + stdout, stderr, status = with_env 'NO_COVERAGE' => '1' do |
| 36 | + options = %w[ -w ] |
| 37 | + options << "--disable=gem" if RUBY_VERSION.to_f >= 1.9 && RSpec::Support::Ruby.mri? |
| 38 | + run_ruby_with_current_load_path(command, *options) |
| 39 | + end |
| 40 | + |
| 41 | + # Ignore bundler warning. |
| 42 | + stderr = stderr.split("\n").reject { |l| l =~ %r{bundler/source/rubygems} }.join("\n") |
| 43 | + [stdout, stderr, status.exitstatus] |
| 44 | + end |
| 45 | + |
| 46 | + define_method :load_all_lib_files do |
| 47 | + files = all_lib_files - lib_test_env_files |
| 48 | + preamble = ['orig_loaded_features = $".dup', preamble_for_lib] |
| 49 | + postamble = [ |
| 50 | + 'loaded_features = ($" - orig_loaded_features).join("\n")', |
| 51 | + "File.open('#{loaded_features_tempfile.path}', 'w') { |f| f.write(loaded_features) }" |
| 52 | + ] |
| 53 | + |
| 54 | + load_all_files(files, preamble, postamble) |
| 55 | + end |
| 56 | + |
| 57 | + define_method :load_all_spec_files do |
| 58 | + files = files_to_require_for("spec") + lib_test_env_files |
| 59 | + load_all_files(files, preamble_for_spec) |
| 60 | + end |
| 61 | + |
| 62 | + attr_reader :loaded_features_tempfile, :all_lib_files, :lib_test_env_files, |
| 63 | + :lib_file_results, :spec_file_results |
| 64 | + |
| 65 | + before(:context) do |
| 66 | + @loaded_features_tempfile = Tempfile.new("loaded_features.txt") |
| 67 | + @all_lib_files = files_to_require_for("lib") |
| 68 | + @lib_test_env_files = all_lib_files.grep(consider_a_test_env_file) |
| 69 | + |
| 70 | + @lib_file_results, @spec_file_results = [ |
| 71 | + # Load them in parallel so it's faster... |
| 72 | + Thread.new { load_all_lib_files }, |
| 73 | + Thread.new { load_all_spec_files } |
| 74 | + ].map(&:join).map(&:value) |
| 75 | + end |
| 76 | + |
| 77 | + def have_successful_no_warnings_output |
| 78 | + eq ["", "", 0] |
| 79 | + end |
| 80 | + |
| 81 | + it "issues no warnings when loaded", :slow do |
| 82 | + expect(lib_file_results).to have_successful_no_warnings_output |
| 83 | + end |
| 84 | + |
| 85 | + it "issues no warnings when the spec files are loaded", :slow do |
| 86 | + expect(spec_file_results).to have_successful_no_warnings_output |
| 87 | + end |
| 88 | + |
| 89 | + it 'only loads a known set of stdlibs so gem authors are forced ' \ |
| 90 | + 'to load libs they use to have passing specs', :slow do |
| 91 | + loaded_features = File.read(loaded_features_tempfile.path).split("\n") |
| 92 | + if RUBY_VERSION == '1.8.7' |
| 93 | + # On 1.8.7, $" returns the relative require path if that was used |
| 94 | + # to require the file. LIB_REGEX will not match the relative version |
| 95 | + # since it has a `/lib` prefix. Here we deal with this by expanding |
| 96 | + # relative files relative to the $LOAD_PATH dir (lib). |
| 97 | + Dir.chdir("lib") { loaded_features.map! { |f| File.expand_path(f) } } |
| 98 | + end |
| 99 | + |
| 100 | + loaded_features.reject! { |feature| RSpec::CallerFilter::LIB_REGEX =~ feature } |
| 101 | + loaded_features.reject! { |feature| /diff\/lcs/ =~ feature } |
| 102 | + loaded_features.reject! { |feature| allowed_loaded_feature_regexps.any? { |r| r =~ feature } } |
| 103 | + |
| 104 | + expect(loaded_features).to eq([]) |
| 105 | + end |
| 106 | +end |
0 commit comments