diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e6a9941a8..075d330af1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,19 @@ on: branches: - '*' jobs: + rubocop: + name: Rubocop + runs-on: 'ubuntu-20.04' + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + - run: script/update_rubygems_and_install_bundler + - run: bundle install --standalone + - run: bundle binstubs --all + - run: script/run_rubocop + test: name: 'Ruby: ${{ matrix.ruby }}, Rails: ${{ matrix.env.RAILS_VERSION }}' runs-on: ubuntu-20.04 @@ -16,19 +29,11 @@ jobs: fail-fast: false matrix: include: - # Rails Master (7.0) builds >= 2.7 - - ruby: 3.0 - allow_failure: true - env: - RAILS_VERSION: 'master' - - ruby: 2.7 - allow_failure: true - env: - RAILS_VERSION: 'master' - # Rails 6.1 builds >= 2.5 - - ruby: 3.0 - allow_failure: true + - ruby: 3.1 + env: + RAILS_VERSION: '~> 6.1.0' + - ruby: '3.0' env: RAILS_VERSION: '~> 6.1.0' - ruby: 2.7 @@ -40,9 +45,14 @@ jobs: - ruby: 2.5 env: RAILS_VERSION: '~> 6.1.0' + - ruby: jruby-9.3.1.0 + env: + RAILS_VERSION: '~> 6.1.0' + JRUBY_OPT: '--dev' + JAVA_OPTS: '--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED' # Rails 6.0 builds >= 2.5.0 - - ruby: 3.0 + - ruby: '3.0' env: RAILS_VERSION: '~> 6.0.0' - ruby: 2.7 @@ -54,6 +64,12 @@ jobs: - ruby: 2.5 env: RAILS_VERSION: '~> 6.0.0' + - ruby: jruby-9.3.1.0 + env: + RAILS_VERSION: '~> 6.0.0' + JRUBY_OPT: '--dev' + JAVA_OPTS: '--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED' + # Rails 5.2 builds >= 2.2.2 - ruby: 2.6.6 env: @@ -74,12 +90,19 @@ jobs: - ruby: 2.2.10 env: RAILS_VERSION: '5-2-stable' + - ruby: jruby-9.3.1.0 + env: + RAILS_VERSION: '~> 5.2.0' + JRUBY_OPT: '--dev' + JAVA_OPTS: '--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED' + env: ${{ matrix.env }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + - run: script/update_rubygems_and_install_bundler - run: script/clone_all_rspec_repos - run: bundle install --binstubs - run: script/run_build diff --git a/.rubocop.yml b/.rubocop.yml index af0e19fb11..95832a486d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,7 +5,9 @@ AllCops: Exclude: # Templates are really ERB which Rubocop does not parse - bin/**/* + - bundle/**/* - lib/generators/rspec/*/templates/**/* + - tmp/**/* Bundler/DuplicatedGem: Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8221da2914..19abcfbf04 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -9,3 +9,7 @@ Layout/LineLength: # Over time we'd like to get this down, but this is what we're at now. Metrics/MethodLength: Max: 43 # default: 10 + +Metrics/ClassLength: + Exclude: + - lib/rspec/rails/matchers/have_enqueued_mail.rb diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1d48088034..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,69 +0,0 @@ -language: ruby - -sudo: required -addons: - chrome: stable - -# We deviate from the rspec-dev cache setting here. -# -# Travis has special bundler support where it knows to run `bundle clean` -# before backing up the bundle so it doesn't grow indefinitely. -# -# For the other repos we changed to specifying the directory specifically to -# avoid the `bundle clean` travis was doing, because Myron found that, when -# running one of the a build for one repo, the "run the rspec-core and -# rspec-rails spec suites" parts would take a while because it would _always_ -# have to install fresh gems (they weren't in the cache). This was happening -# because travis was running `bundle clean` (due to the previous `cache: -# bundler` config) against one repo which was was pruning gems installed for -# the other repos. -# -# In our case, we aren't running the spec suites for the other repos so the -# original `cache: bundler` is a better option. -cache: bundler - -bundler_args: "--binstubs --path ../bundle --retry=3 --jobs=3" - -before_install: - - script/install_chrome_driver - - script/update_rubygems_and_install_bundler - - script/clone_all_rspec_repos - -before_script: - # Rails 4+ complains with a bundler rails binstub in PROJECT_ROOT/bin/ - - rm -f bin/rails - - bundle exec rails --version - -script: "script/run_build 2>&1" - -matrix: - include: - # Rails 6.1 builds >= 2.5 - - jdk: oraclejdk11 - env: - - RAILS_VERSION='master' - - JRUBY_OPT=--dev - - JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED" - - # Rails 6.0 builds >= 2.5.0 - - rvm: jruby-head - jdk: oraclejdk11 - env: - - RAILS_VERSION='~> 6.0.0' - - JRUBY_OPT=--dev - - JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED" - - - rvm: jruby-head - jdk: oraclejdk11 - env: - - RAILS_VERSION='~> 5.2.0' - - JRUBY_OPT=--dev - - JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED" - - fast_finish: true - -branches: - only: - - master - - /^\d+-\d+-maintenance$/ - - /^\d+-\d+-dev$/ diff --git a/Gemfile-rails-dependencies b/Gemfile-rails-dependencies index 9478abaf8a..0d276d004a 100644 --- a/Gemfile-rails-dependencies +++ b/Gemfile-rails-dependencies @@ -34,4 +34,15 @@ else gem "puma" gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby] gem 'selenium-webdriver', require: false + + # This is required for Ruby 3.1 and Rails 6.1.x as of time + # of writing, because in Ruby 3.1 these gems are no longer + # automatically included. This issue was fixed on the Rails + # side in Rails 7.0.1, but is not yet fixed in the Rails 6.1.x + # branch. Discussion can be found here - https://github.com/mikel/mail/pull/1439 + if RUBY_VERSION >= '3.1' && version.split(' ').last < '7.0' + gem 'net-smtp', require: false + gem 'net-imap', require: false + gem 'net-pop', require: false + end end diff --git a/lib/rspec/rails/feature_check.rb b/lib/rspec/rails/feature_check.rb index 2570d6659b..11558046e0 100644 --- a/lib/rspec/rails/feature_check.rb +++ b/lib/rspec/rails/feature_check.rb @@ -39,6 +39,10 @@ def has_action_mailbox? defined?(::ActionMailbox) end + def ruby_3_1? + RUBY_VERSION >= "3.1" + end + def type_metatag(type) "type: :#{type}" end diff --git a/lib/rspec/rails/matchers/have_enqueued_mail.rb b/lib/rspec/rails/matchers/have_enqueued_mail.rb index b7756346e9..2614631d4f 100644 --- a/lib/rspec/rails/matchers/have_enqueued_mail.rb +++ b/lib/rspec/rails/matchers/have_enqueued_mail.rb @@ -76,7 +76,7 @@ def job_match?(job) def arguments_match?(job) @args = if @mail_args.any? - base_mailer_args + @mail_args + base_mailer_args + process_arguments(job, @mail_args) elsif @mailer_class && @method_name base_mailer_args + [any_args] elsif @mailer_class @@ -88,12 +88,38 @@ def arguments_match?(job) super(job) end + def process_arguments(job, given_mail_args) + # Old matcher behavior working with all builtin classes but ActionMailer::MailDeliveryJob + return given_mail_args if use_given_mail_args?(job) + + # If matching args starts with a hash and job instance has params match with them + if given_mail_args.first.is_a?(Hash) && job[:args][3]['params'].present? + [hash_including(params: given_mail_args[0], args: given_mail_args.drop(1))] + else + [hash_including(args: given_mail_args)] + end + end + + def use_given_mail_args?(job) + return true if FeatureCheck.has_action_mailer_parameterized? && job[:job] <= ActionMailer::Parameterized::DeliveryJob + return false if FeatureCheck.ruby_3_1? + + !(FeatureCheck.has_action_mailer_unified_delivery? && job[:job] <= ActionMailer::MailDeliveryJob) + end + def base_mailer_args [mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD] end def yield_mail_args(block) - proc { |*job_args| block.call(*(job_args - base_mailer_args)) } + proc do |*job_args| + mailer_args = job_args - base_mailer_args + if mailer_args.first.is_a?(Hash) + block.call(*mailer_args.first[:args]) + else + block.call(*mailer_args) + end + end end def check_active_job_adapter @@ -120,16 +146,23 @@ def unmatching_mail_jobs_message def mail_job_message(job) mailer_method = job[:args][0..1].join('.') - - mailer_args = job[:args][3..-1] + mailer_args = deserialize_arguments(job)[3..-1] + mailer_args = mailer_args.first[:args] if unified_mail?(job) msg_parts = [] - msg_parts << "with #{mailer_args}" if mailer_args.any? + display_args = display_mailer_args(mailer_args) + msg_parts << "with #{display_args}" if display_args.any? msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers' msg_parts << "at #{Time.at(job[:at])}" if job[:at] "#{mailer_method} #{msg_parts.join(', ')}".strip end + def display_mailer_args(mailer_args) + return mailer_args unless mailer_args.first.is_a?(Hash) && mailer_args.first.key?(:args) + + mailer_args.first[:args] + end + def legacy_mail?(job) job[:job] <= ActionMailer::DeliveryJob end diff --git a/script/predicate_functions.sh b/script/predicate_functions.sh index 746a4985f1..1a3198801d 100644 --- a/script/predicate_functions.sh +++ b/script/predicate_functions.sh @@ -97,11 +97,3 @@ function documentation_enforced { return 1 fi } - -function style_and_lint_enforced { - if [ -x ./bin/rubocop ]; then - return 0 - else - return 1 - fi -} diff --git a/script/run_build b/script/run_build index b7e542bfba..2938f81c8a 100755 --- a/script/run_build +++ b/script/run_build @@ -26,10 +26,6 @@ if documentation_enforced; then fold "doc check" check_documentation_coverage fi -if style_and_lint_enforced; then - fold "rubocop" check_style_and_lint -fi - if is_mri; then fold "one-by-one specs" run_specs_one_by_one run_all_spec_suites diff --git a/script/run_rubocop b/script/run_rubocop new file mode 100755 index 0000000000..c2d94dcca3 --- /dev/null +++ b/script/run_rubocop @@ -0,0 +1,14 @@ +#!/bin/bash +# This file was generated on 2022-01-10T22:24:10+00:00 from the rspec-dev repo. +# DO NOT modify it by hand as your changes will get lost the next time it is generated. + +set -e +source script/functions.sh + +# Allow repos to override the default functions and add their own +if [ -f script/custom_build_functions.sh ]; then + source script/custom_build_functions.sh +fi + + +fold "rubocop" check_style_and_lint diff --git a/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb b/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb index 152e02fffc..119779352e 100644 --- a/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb +++ b/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb @@ -393,18 +393,22 @@ def self.name; "NonMailerJob"; end }.to have_enqueued_mail(UnifiedMailer, :test_email).and have_enqueued_mail(UnifiedMailer, :email_with_args) end - it "passes with provided argument matchers" do + it "matches arguments when mailer has only args" do + expect { + UnifiedMailer.email_with_args(1, 2).deliver_later + }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with(1, 2) + end + + it "matches arguments when mailer is parameterized" do expect { UnifiedMailer.with('foo' => 'bar').test_email.deliver_later - }.to have_enqueued_mail(UnifiedMailer, :test_email).with( - a_hash_including(params: {'foo' => 'bar'}) - ) + }.to have_enqueued_mail(UnifiedMailer, :test_email).with('foo' => 'bar') + end + it "matches arguments when mixing parameterized and non-parameterized emails" do expect { UnifiedMailer.with('foo' => 'bar').email_with_args(1, 2).deliver_later - }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with( - a_hash_including(params: {'foo' => 'bar'}, args: [1, 2]) - ) + }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with({'foo' => 'bar'}, 1, 2) end it "passes when using a mailer with `delivery_job` set to a sub class of `ActionMailer::DeliveryJob`" do