diff --git a/Rakefile b/Rakefile index 3bc1b563e1..7dca66359d 100644 --- a/Rakefile +++ b/Rakefile @@ -4,8 +4,13 @@ require "bundler" require "bundler/gem_tasks" Bundler.setup +ROOT = File.expand_path(File.join(File.dirname(__FILE__))) + +$: << File.join(ROOT, 'spec/shared/lib') + require "rake" require "rspec/core/rake_task" +require 'mrss/spec_organizer' $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require "mongoid/version" @@ -38,11 +43,30 @@ RSpec::Core::RakeTask.new('spec:progress') do |spec| spec.pattern = "spec/**/*_spec.rb" end -task :ci do - $LOAD_PATH.push File.expand_path("../spec", __FILE__) - require 'support/spec_organizer' +CLASSIFIERS = [ + [%r,^mongoid/attribute,, :attributes], + [%r,^mongoid/association/[or],, :associations_referenced], + [%r,^mongoid/association,, :associations], + [%r,^mongoid,, :unit], + [%r,^integration,, :integration], + [%r,^rails,, :rails], +] + +RUN_PRIORITY = %i( + unit attributes associations_referenced associations + integration rails +) + +def spec_organizer + Mrss::SpecOrganizer.new( + root: ROOT, + classifiers: CLASSIFIERS, + priority_order: RUN_PRIORITY, + ) +end - SpecOrganizer.new.run +task :ci do + spec_organizer.run end task :default => :spec diff --git a/spec/integration/app_spec.rb b/spec/integration/app_spec.rb index 946b0258d7..1e7fc8236d 100644 --- a/spec/integration/app_spec.rb +++ b/spec/integration/app_spec.rb @@ -13,7 +13,7 @@ end require 'fileutils' - require 'support/child_process_helper' + require 'mrss/child_process_helper' require 'open-uri' FileUtils.mkdir_p(TMP_BASE) @@ -91,19 +91,19 @@ ['~> 5.1.0', '~> 5.2.0', '~> 6.0.0'].each do |rails_version| context "with rails #{rails_version}" do it 'creates' do - ChildProcessHelper.check_call(%w(gem uni rails -a)) - ChildProcessHelper.check_call(%w(gem install rails --no-document -v) + [rails_version]) + Mrss::ChildProcessHelper.check_call(%w(gem uni rails -a)) + Mrss::ChildProcessHelper.check_call(%w(gem install rails --no-document -v) + [rails_version]) Dir.chdir(TMP_BASE) do FileUtils.rm_rf('mongoid-test') - ChildProcessHelper.check_call(%w(rails new mongoid-test --skip-spring --skip-active-record), env: clean_env) + Mrss::ChildProcessHelper.check_call(%w(rails new mongoid-test --skip-spring --skip-active-record), env: clean_env) Dir.chdir('mongoid-test') do adjust_app_gemfile - ChildProcessHelper.check_call(%w(bundle install), env: clean_env) + Mrss::ChildProcessHelper.check_call(%w(bundle install), env: clean_env) - ChildProcessHelper.check_call(%w(rails g model post), env: clean_env) - ChildProcessHelper.check_call(%w(rails g model comment post:belongs_to), env: clean_env) + Mrss::ChildProcessHelper.check_call(%w(rails g model post), env: clean_env) + Mrss::ChildProcessHelper.check_call(%w(rails g model comment post:belongs_to), env: clean_env) # https://jira.mongodb.org/browse/MONGOID-4885 comment_text = File.read('app/models/comment.rb') @@ -136,7 +136,7 @@ before do Dir.chdir(APP_PATH) do remove_bundler_req - ChildProcessHelper.check_call(%w(bundle install), env: env) + Mrss::ChildProcessHelper.check_call(%w(bundle install), env: env) write_mongoid_yml end @@ -150,7 +150,7 @@ end index.should be nil - ChildProcessHelper.check_call(%w(rake db:mongoid:create_indexes), + Mrss::ChildProcessHelper.check_call(%w(rake db:mongoid:create_indexes), cwd: APP_PATH, env: env) index = client['posts'].indexes.detect do |index| @@ -168,10 +168,10 @@ def clone_application(repo_url, subdir: nil, rails_version: nil) Dir.chdir(TMP_BASE) do FileUtils.rm_rf(File.basename(repo_url)) - ChildProcessHelper.check_call(%w(git clone) + [repo_url]) + Mrss::ChildProcessHelper.check_call(%w(git clone) + [repo_url]) Dir.chdir(File.join(*[File.basename(repo_url), subdir].compact)) do adjust_app_gemfile(rails_version: rails_version) - ChildProcessHelper.check_call(%w(bundle install), env: clean_env) + Mrss::ChildProcessHelper.check_call(%w(bundle install), env: clean_env) puts `git diff` write_mongoid_yml @@ -230,7 +230,7 @@ def remove_spring # in `initialize': too long unix socket path (126bytes given but 108bytes max) (ArgumentError) # Is it trying to create unix sockets in current directory? # https://stackoverflow.com/questions/30302021/rails-runner-without-spring - ChildProcessHelper.check_call(%w(bin/spring binstub --remove --all), env: clean_env) + Mrss::ChildProcessHelper.check_call(%w(bin/spring binstub --remove --all), env: clean_env) end def clean_env diff --git a/spec/shared b/spec/shared index 2d1d703bff..8cc5a3f702 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 2d1d703bff613f891460359d8b15f6a0256dd14f +Subproject commit 8cc5a3f70266166fef21c60ffb5bc5b3dcd1a7c9 diff --git a/spec/support/child_process_helper.rb b/spec/support/child_process_helper.rb deleted file mode 100644 index ec23f8c9bb..0000000000 --- a/spec/support/child_process_helper.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -gem 'childprocess' -require 'childprocess' -require 'tempfile' - -module ChildProcessHelper - class SpawnError < StandardError; end - - module_function def call(cmd, env: nil, cwd: nil) - process = ChildProcess.new(*cmd) - process.io.inherit! - if cwd - process.cwd = cwd - end - if env - env.each do |k, v| - process.environment[k.to_s] = v - end - end - process.start - process.wait - process - end - - module_function def check_call(cmd, env: nil, cwd: nil) - process = call(cmd, env: env, cwd: cwd) - unless process.exit_code == 0 - raise "Failed to execute: #{cmd}" - end - end - - module_function def get_output(cmd, env: nil, cwd: nil) - process = ChildProcess.new(*cmd) - process.io.inherit! - if cwd - process.cwd = cwd - end - if env - env.each do |k, v| - process.environment[k.to_s] = v - end - end - - output = '' - r, w = IO.pipe - - begin - process.io.stdout = w - process.start - w.close - - thread = Thread.new do - begin - loop do - output << r.readpartial(16384) - end - rescue EOFError - end - end - - process.wait - thread.join - ensure - r.close - end - - [process, output] - end - - module_function def check_output(*args) - process, output = get_output(*args) - unless process.exit_code == 0 - raise "Failed to execute: #{args}" - end - output - end -end diff --git a/spec/support/spec_organizer.rb b/spec/support/spec_organizer.rb deleted file mode 100644 index 17a8d3cea6..0000000000 --- a/spec/support/spec_organizer.rb +++ /dev/null @@ -1,130 +0,0 @@ -require 'support/child_process_helper' -require 'json' - -autoload :FileUtils, 'fileutils' -autoload :Find, 'find' - -# Organizes and runs all of the tests in the test suite in batches. -# -# Organizing the tests in batches serves two purposes: -# -# 1. This allows running unit tests before integration tests, therefore -# in theory revealing failures quicker on average. -# 2. This allows running some tests that have high intermittent failure rate -# in their own test process. -# -# This class aggregates RSpec results after the test runs. -class SpecOrganizer - CLASSIFIERS = [ - [%r,^mongoid/attribute,, :attributes], - [%r,^mongoid/association/[or],, :associations_referenced], - [%r,^mongoid/association,, :associations], - [%r,^mongoid,, :unit], - [%r,^integration,, :integration], - [%r,^rails,, :rails], - ] - - RUN_PRIORITY = %i( - unit attributes associations_referenced associations - integration rails - ) - - SPEC_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) - ROOT = File.expand_path(File.join(SPEC_ROOT, '..')) - RSPEC_JSON_PATH = File.join(ROOT, 'tmp/rspec.json') - RSPEC_ALL_JSON_PATH = File.join(ROOT, 'tmp/rspec-all.json') - - def run - FileUtils.rm_f(RSPEC_ALL_JSON_PATH) - - buckets = {} - Find.find(SPEC_ROOT) do |path| - next unless File.file?(path) - next unless path =~ /_spec\.rb\z/ - rel_path = path[(SPEC_ROOT.length + 1)..path.length] - - found = false - CLASSIFIERS.each do |(regexp, category)| - if regexp =~ rel_path - buckets[category] ||= [] - buckets[category] << rel_path - found = true - break - end - end - - unless found - buckets[nil] ||= [] - buckets[nil] << rel_path - end - end - - failed = [] - - RUN_PRIORITY.each do |category| - if files = buckets.delete(category) - unless run_files(category, files) - failed << category - end - end - end - if files = buckets.delete(nil) - unless run_files('remaining', files) - failed << 'remaining' - end - end - - unless buckets.empty? - raise "Some buckets were not executed: #{buckets.keys.map(&:to_s).join(', ')}" - end - - if failed.any? - raise "The following buckets failed: #{failed.map(&:to_s).join(', ')}" - end - end - - def run_files(category, paths) - paths = paths.map do |path| - File.join('spec', path) - end - - puts "Running #{category.to_s.gsub('_', ' ')} tests" - FileUtils.rm_f(RSPEC_JSON_PATH) - cmd = %w(rspec) + paths - - begin - ChildProcessHelper.check_call(cmd) - ensure - if File.exist?(RSPEC_JSON_PATH) - if File.exist?(RSPEC_ALL_JSON_PATH) - merge_rspec_results - else - FileUtils.cp(RSPEC_JSON_PATH, RSPEC_ALL_JSON_PATH) - end - end - end - - true - rescue ChildProcessHelper::SpawnError - false - end - - def merge_rspec_results - all = JSON.parse(File.read(RSPEC_ALL_JSON_PATH)) - new = JSON.parse(File.read(RSPEC_JSON_PATH)) - all['examples'] += new.delete('examples') - new.delete('summary').each do |k, v| - all['summary'][k] += v - end - new.delete('version') - new.delete('summary_line') - unless new.empty? - raise "Unhandled rspec results keys: #{new.keys.join(', ')}" - end - # We do not merge summary lines, delete them from aggregated results - all.delete('summary_line') - File.open(RSPEC_ALL_JSON_PATH, 'w') do |f| - f << JSON.dump(all) - end - end -end