diff --git a/.github/workflows/monkey.yml b/.github/workflows/monkey.yml new file mode 100644 index 0000000000..ad08acc452 --- /dev/null +++ b/.github/workflows/monkey.yml @@ -0,0 +1,22 @@ +--- +name: Check Monkey Patches + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + env: + CI: true + TESTOPTS: "-v" + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby 3.0 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.0 + bundler-cache: true + - name: bundle + run: bundle install --jobs 4 --retry 3 + - name: Run RSpec Monkey Patch check + run: bundle exec rspec spec/check_monkey_patches.rb diff --git a/spec/check_monkey_patches.rb b/spec/check_monkey_patches.rb new file mode 100644 index 0000000000..99f3a41bb2 --- /dev/null +++ b/spec/check_monkey_patches.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +raise 'Monkey patches spec must be run before loading Mongoid' if defined?(Mongoid::Document) + +require 'rspec' + +# Require dependency gems that add monkey patches +require 'active_support/all' +require 'bson' +require 'openssl' +require 'ruby2_keywords' + +CLASSES = [ + Object, + Array, + BigDecimal, + Date, + DateTime, + FalseClass, + Float, + Hash, + Integer, + Module, + NilClass, + Range, + Regexp, + Set, + String, + Symbol, + Time, + TrueClass, + ActiveSupport::TimeWithZone, + BSON::Binary, + BSON::Decimal128, + BSON::ObjectId, + BSON::Regexp, + BSON::Regexp::Raw +].freeze + +instance_methods = CLASSES.index_with(&:instance_methods) +class_methods = CLASSES.index_with(&:methods) + +require 'mongoid' + +ADDED_INSTANCE_METHODS = instance_methods.each_with_object({}) do |(klass, methods), hash| + hash[klass] = klass.instance_methods.sort - methods - (hash[Object] || []) +end + +ADDED_CLASS_METHODS = class_methods.each_with_object({}) do |(klass, methods), hash| + hash[klass] = klass.methods.sort - methods - (hash[Object] || []) - ADDED_INSTANCE_METHODS[Object] +end + +EXPECTED_INSTANCE_METHODS = { + Object => %i[ + __add__ + __add_from_array__ + __array__ + __deep_copy__ + __evolve_object_id__ + __expand_complex__ + __find_args__ + __intersect__ + __intersect_from_array__ + __intersect_from_object__ + __mongoize_object_id__ + __mongoize_time__ + __setter__ + __sortable__ + __to_inc__ + __union__ + __union_from_object__ + blank_criteria + blank_criteria? + do_or_do_not + ivar + mongoize + multi_arged? + numeric? + regexp? + remove_ivar + resizable? + substitutable + you_must + ].freeze, + Array => %i[ + __evolve_date__ + __evolve_time__ + __sort_option__ + __sort_pair__ + delete_one + ].freeze, + Date => %i[ + __evolve_date__ + __evolve_time__ + ].freeze, + DateTime => %i[ + __evolve_date__ + __evolve_time__ + ].freeze, + Float => %i[ + __evolve_date__ + __evolve_time__ + ].freeze, + Hash => %i[ + __consolidate__ + __sort_option__ + _mongoid_unsatisfiable_criteria? + delete_id + extract_id + to_criteria + ].freeze, + Integer => %i[ + __evolve_date__ + __evolve_time__ + unconvertable_to_bson? + ].freeze, + Module => %i[ + re_define_method + ].freeze, + NilClass => %i[ + __evolve_date__ + __evolve_time__ + __expanded__ + __override__ + collectionize + ].freeze, + Range => %i[ + __evolve_date__ + __evolve_range__ + __evolve_time__ + ].freeze, + String => %i[ + __evolve_date__ + __evolve_time__ + __expr_part__ + __mongo_expression__ + __sort_option__ + collectionize + mongoid_id? + unconvertable_to_bson + unconvertable_to_bson= + unconvertable_to_bson? + ].freeze, + Symbol => %i[ + __expr_part__ + add_to_set + all + asc + ascending + avg + desc + descending + elem_match + eq + exists + first + gt + gte + in + intersects_line + intersects_point + intersects_polygon + last + lt + lte + max + min + mod + mongoid_id? + ne + near + near_sphere + nin + not + push + sum + with_size + with_type + within_box + within_polygon + ].freeze, + Time => %i[ + __evolve_date__ + __evolve_time__ + ].freeze, + ActiveSupport::TimeWithZone => %i[ + __evolve_date__ + __evolve_time__ + _bson_to_i + bson_type + to_bson + ].freeze, + BSON::Decimal128 => %i[ + __evolve_decimal128__ + ].freeze +}.freeze + +EXPECTED_CLASS_METHODS = { + Object => %i[ + __mongoize_fk__ + demongoize + evolve + re_define_method + ].freeze, + Float => %i[__numeric__].freeze, + Integer => %i[__numeric__].freeze, + String => %i[__expr_part__].freeze, + Symbol => %i[add_key].freeze, + Time => %i[configured].freeze +}.freeze + +RSpec.describe('Monkey patches') do + + CLASSES.each do |klass| + + context klass.name do + it 'adds no unexpected instance methods' do + expect(ADDED_INSTANCE_METHODS[klass]).to eq(EXPECTED_INSTANCE_METHODS[klass]&.sort || []) + end + + it 'adds no unexpected class methods' do + expect(ADDED_CLASS_METHODS[klass]).to eq(EXPECTED_CLASS_METHODS[klass]&.sort || []) + end + end + end +end