diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 054b14f1..d206cadf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,20 +10,45 @@ on: # This allows a subsequently queued workflow run to interrupt previous runs. concurrency: - group: "${{ github.workflow }} @ ${{ github.ref }}" + group: '${{ github.workflow }} @ ${{ github.ref }}' cancel-in-progress: true jobs: - # Since the name of the matrix job depends on the version, we define another job with a more stable name. - test_results: - if: ${{ always() }} + orchestrate: + name: Orchestrate matrix from Docker Hub runs-on: ubuntu-latest - name: Test Results - needs: [test] + outputs: + postgis_tags: ${{ steps.prepare.outputs.postgis_tags }} steps: - - run: '[[ "${{ needs.test.result }}" == "success" ]]' + - name: Query Docker Hub tags and build matrix + id: prepare + run: | + # https://www.postgresql.org/support/versioning/ + pg_targets='[14, 15, 16, 17, 18]' + + tags=$( + curl -s "https://hub.docker.com/v2/repositories/postgis/postgis/tags?page_size=100" | + jq --compact-output ' + .results + | map(.name) as $tags + | '"$pg_targets"' + | map( + . as $target + | $tags + | map(select(test("^\($target)-\\d+\\.\\d+$")))[0] + ) + ' + ) + + if [[ $(jq 'length' <<< "$tags") -ne $(jq 'length' <<< "$tags") ]]; then + echo "Error: Could not find tags for all PostgreSQL targets" >&2 + exit 1 + fi + + echo "postgis_tags=$tags" >> $GITHUB_OUTPUT test: runs-on: ubuntu-latest + needs: [orchestrate] services: postgis: image: postgis/postgis:${{matrix.pg}} @@ -38,15 +63,14 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - env: - AR_VERSION: 8.0.0.1 strategy: fail-fast: false matrix: # https://ruby-lang.org/en/downloads/branches - ruby: ["3.4", "3.3", "3.2"] + ruby: ['3.4', '3.3', '3.2'] # https://www.postgresql.org/support/versioning/ - pg: [12-master, 13-master, 14-master, 15-master, 16-master] + # See (and update!) `pg_targets` above for selected pg versions. + pg: ${{ fromJson(needs.orchestrate.outputs.postgis_tags) }} steps: - name: Set Up Actions uses: actions/checkout@v4 @@ -59,11 +83,9 @@ jobs: bundler-cache: true - name: Setup Database run: | - psql -d postgresql://postgres:postgres@localhost:5432/postgres \ - -c "create database postgis_adapter_test" \ - -c "create database activerecord_unittest" \ - -c "create database activerecord_unittest2" - for db in postgis_adapter_test activerecord_unittest activerecord_unittest2; do + for db in activerecord_unittest activerecord_unittest2; do + psql -d postgresql://postgres:postgres@localhost:5432/postgres \ + -c "create database $db" psql -d postgresql://postgres:postgres@localhost:5432/$db -c "create extension postgis" done - name: Run Tests @@ -74,3 +96,86 @@ jobs: PGPASSWORD: postgres TESTOPTS: --profile=3 TEST_TIMEOUT: 30 + RAILS_MINITEST_PLUGIN: '1' + JSON_REPORTER: 'report.json' + - name: Upload Report + if: ${{ failure() && steps.test.conclusion == 'failure' }} + uses: actions/upload-artifact@v4 + with: + name: report-${{ matrix.pg }}-${{ matrix.ruby }} + path: report.json + # Since the name of the matrix job depends on the version, + # we define another job with a more stable name. This jobs + # also gives a nice summary of failing tests. + test_results: + if: always() + runs-on: ubuntu-latest + name: Test Results + needs: [test] + steps: + - name: Check Success + run: | + result="${{ needs.test.result }}" + + if [[ $result == "success" || $result == "skipped" ]]; then + echo "All tests passed :taco:" >>$GITHUB_STEP_SUMMARY + exit 0 + else + exit 1 + fi + - name: Download Reports + if: failure() + uses: actions/download-artifact@v4 + with: + path: reports + - name: Aggregate Reports + if: failure() + run: | + cat <>$GITHUB_STEP_SUMMARY + # Failing Tests + + + + + + + + + + + EOF + + jq --slurp --raw-output ' + map(.failed_tests) | flatten | map({ + klass, + NAME, + failure: .failures[0], + source_url: ( + .source_location | if (.[0] | contains("/gems/")) then + (.[0] | capture("rails-(?.*?)/(?.*)")) * + {line: .[1], server: "https://github.com", repo: "rails/rails"} + else + (.[0] | capture("activerecord-postgis-adapter/(?test/.*)") * + {line: .[1], sha: $ENV.GITHUB_SHA, repo: $ENV.GITHUB_REPOSITORY, server: $ENV.GITHUB_SERVER_URL} + end | "\(.server)/\(.repo)/blob/\(.sha)/\(.path)#L\(.line)" + ) + }) | group_by(.) | map(.[0] * { count: length }) | sort[0:100][] + | "" + + "" + + "" + + "" + + "" + ' reports/*/report.json >>$GITHUB_STEP_SUMMARY + + cat <>$GITHUB_STEP_SUMMARY + +
#TestFailure
\(.count)\(.klass)#\(.NAME)
\(.failure)
+ EOF + + # Do not print json if too large. + [[ "$(du -s reports | cut -f1)" -gt 124 ]] && exit 0 + + echo >>$GITHUB_STEP_SUMMARY + echo '```json' >>$GITHUB_STEP_SUMMARY + jq --slurp --compact-output '.' reports/*/report.json >>$GITHUB_STEP_SUMMARY + echo '```' >>$GITHUB_STEP_SUMMARY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b6ff06d..d2fd9c4b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,12 +4,14 @@ Fork the repo: `git clone git@github.com:rgeo/activerecord-postgis-adapter.git` -Set up your test database: +Set up your test databases: ```sh createuser -s postgres -psql -U postgres -c "create database postgis_adapter_test" -psql -U postgres -d postgis_adapter_test -c "create extension postgis" +for db in activerecord_unittest activerecord_unittest2; do + psql -U postgres -c "create database $db" + psql -U postgres -d $db -c "create extension postgis" +done ``` You may also set up environment variables to define the database connection. @@ -21,10 +23,11 @@ export PGUSER=postgis_test export PGPASSWORD=password123 export PGPORT=95432 export PGHOST=127.0.0.2 -export PGDATABASE=postgis_adapter_test -psql -c "create database postgis_adapter_test" -psql -c "create extension postgis" +for db in activerecord_unittest activerecord_unittest2; do + psql -U postgres -c "create database $db" + psql -U postgres -d $db -c "create extension postgis" +done ``` Install dependencies: @@ -40,8 +43,8 @@ Make sure the tests pass: Run tests with a specific ActiveRecord version: ```sh -AR_VERSION=7.0.1 bundle install -AR_VERSION=7.0.1 bundle exec rake test +AR_VERSION= bundle install +AR_VERSION= bundle exec rake test ``` To run a specific test, use the `POSTGIS_TEST_FILES` environment variable: diff --git a/Gemfile b/Gemfile index 5976f205..6ba0b9f0 100644 --- a/Gemfile +++ b/Gemfile @@ -7,8 +7,6 @@ gem "pg", "~> 1.0", platform: :ruby gem "byebug" if ENV["BYEBUG"] def activerecord_version - return ENV["AR_VERSION"] if ENV["AR_VERSION"] - require "uri" require "yaml" require "net/http" @@ -29,7 +27,7 @@ def activerecord_version ver["number"] end # Need to install for tests -gem "rails", github: "rails/rails", tag: "v#{activerecord_version}" +gem "rails", github: "rails/rails", ref: ENV.fetch("AR_VERSION", "v#{activerecord_version}") group :development do # Gems used by the ActiveRecord test suite diff --git a/activerecord-postgis-adapter.gemspec b/activerecord-postgis-adapter.gemspec index 53cf6952..6174e12f 100644 --- a/activerecord-postgis-adapter.gemspec +++ b/activerecord-postgis-adapter.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |spec| spec.summary = "ActiveRecord adapter for PostGIS, based on RGeo." spec.description = "ActiveRecord connection adapter for PostGIS. It is based on the stock " \ - "PostgreSQL adapter, and adds built-in support for the spatial extensions "\ + "PostgreSQL adapter, and adds built-in support for the spatial extensions " \ "provided by PostGIS. It uses the RGeo library to represent spatial data in Ruby." spec.version = ActiveRecord::ConnectionAdapters::PostGIS::VERSION @@ -20,8 +20,8 @@ Gem::Specification.new do |spec| # ruby-lang.org/en/downloads/branches spec.required_ruby_version = ">= 3.2.0" - spec.add_dependency "activerecord", "~> 8.0.0" - spec.add_dependency "rgeo-activerecord", "~> 8.0.0" + spec.add_dependency "activerecord", "~> 8.1.0" + spec.add_dependency "rgeo-activerecord", "~> 8.1.0" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "minitest", "~> 5.4" diff --git a/lib/active_record/connection_adapters/postgis/spatial_column.rb b/lib/active_record/connection_adapters/postgis/column.rb similarity index 67% rename from lib/active_record/connection_adapters/postgis/spatial_column.rb rename to lib/active_record/connection_adapters/postgis/column.rb index 9457e390..bb4d64d0 100644 --- a/lib/active_record/connection_adapters/postgis/spatial_column.rb +++ b/lib/active_record/connection_adapters/postgis/column.rb @@ -3,14 +3,15 @@ module ActiveRecord # :nodoc: module ConnectionAdapters # :nodoc: module PostGIS # :nodoc: - class SpatialColumn < ConnectionAdapters::PostgreSQLColumn # :nodoc: + class Column < PostgreSQL::Column # :nodoc: # sql_type examples: # "Geometry(Point,4326)" # "Geography(Point,4326)" - def initialize(name, default, sql_type_metadata = nil, null = true, + def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, serial: nil, generated: nil, spatial: nil, identity: nil) - @sql_type_metadata = sql_type_metadata + super(name, cast_type, default, sql_type_metadata, null, default_function, + collation: collation, comment: comment, serial: serial, generated: generated, identity: identity) @geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i) if spatial # This case comes from an entry in the geometry_columns table @@ -30,8 +31,6 @@ def initialize(name, default, sql_type_metadata = nil, null = true, # @geometric_type = geo_type_from_sql_type(sql_type) build_from_sql_type(sql_type_metadata.sql_type) end - super(name, default, sql_type_metadata, null, default_function, - collation: collation, comment: comment, serial: serial, generated: generated, identity: identity) if spatial? && @srid @limit = { srid: @srid, type: to_type_name(geometric_type) } @limit[:has_z] = true if @has_z @@ -58,6 +57,49 @@ def spatial? %i[geometry geography].include?(@sql_type_metadata.type) end + def init_with(coder) + @geographic = coder["geographic"] + @geometric_type = coder["geometric_type"] + @has_m = coder["has_m"] + @has_z = coder["has_z"] + @srid = coder["srid"] + @limit = coder["limit"] + super + end + + def encode_with(coder) + coder["geographic"] = @geographic + coder["geometric_type"] = @geometric_type + coder["has_m"] = @has_m + coder["has_z"] = @has_z + coder["srid"] = @srid + coder["limit"] = @limit + super + end + + def ==(other) + other.is_a?(Column) && + super && + other.geographic == geographic && + other.geometric_type == geometric_type && + other.has_m == has_m && + other.has_z == has_z && + other.srid == srid && + other.limit == limit + end + alias :eql? :== + + def hash + Column.hash ^ + super.hash ^ + geographic.hash ^ + geometric_type.hash ^ + has_m.hash ^ + has_z.hash ^ + srid.hash ^ + limit.hash + end + private def set_geometric_type_from_name(name) diff --git a/lib/active_record/connection_adapters/postgis/oid/spatial.rb b/lib/active_record/connection_adapters/postgis/oid/spatial.rb index 46ed619f..d44c933b 100644 --- a/lib/active_record/connection_adapters/postgis/oid/spatial.rb +++ b/lib/active_record/connection_adapters/postgis/oid/spatial.rb @@ -10,12 +10,17 @@ module OID # Responsible for parsing sql_types returned from the database and WKT features. class Spatial < Type::Value def initialize(geo_type: "geometry", srid: 0, has_z: false, has_m: false, geographic: false) - @geo_type = geo_type - @srid = srid - @has_z = has_z - @has_m = has_m - @geographic = geographic + super() + @geographic = geographic.freeze + @factory_attrs = { + geo_type: geo_type.underscore.freeze, + has_m: has_m.freeze, + has_z: has_z.freeze, + srid: srid.freeze, + sql_type: type.to_s.freeze + }.freeze end + protected attr_reader :geographic, :factory_attrs # sql_type: geometry, geometry(Point), geometry(Point,4326), ... # @@ -53,10 +58,9 @@ def self.parse_sql_type(sql_type) end def spatial_factory - @spatial_factory ||= - RGeo::ActiveRecord::SpatialFactoryStore.instance.factory( - factory_attrs - ) + RGeo::ActiveRecord::SpatialFactoryStore.instance.factory( + factory_attrs + ) end def spatial? @@ -80,6 +84,17 @@ def serialize(value) .generate(geo_value) end + def ==(other) + super && + @geographic == other.geographic && + @factory_attrs == other.factory_attrs + end + alias eql? == + + def hash + super ^ [@geographic, @factory_attrs].hash + end + private def cast_value(value) @@ -105,16 +120,6 @@ def wkt_parser(string) RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid) end end - - def factory_attrs - { - geo_type: @geo_type.underscore, - has_m: @has_m, - has_z: @has_z, - srid: @srid, - sql_type: type.to_s - } - end end end end diff --git a/lib/active_record/connection_adapters/postgis/quoting.rb b/lib/active_record/connection_adapters/postgis/quoting.rb index 1d7c8b98..87b1d5c3 100644 --- a/lib/active_record/connection_adapters/postgis/quoting.rb +++ b/lib/active_record/connection_adapters/postgis/quoting.rb @@ -12,6 +12,21 @@ def type_cast(value) super end end + + # NOTE: This method should be private in future rails versions. + # Hence we should also make it private then. + # + # See https://github.com/rails/rails/blob/v8.1.1/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L190 + def lookup_cast_type(sql_type) + type_map.lookup( + # oid + query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i, + # fmod, not needed. + nil, + # details needed for `..::PostGIS::OID::Spatial` (e.g. `geometry(point,3857)`) + sql_type + ) + end end end end diff --git a/lib/active_record/connection_adapters/postgis/schema_statements.rb b/lib/active_record/connection_adapters/postgis/schema_statements.rb index 62f3f754..16b3b06f 100644 --- a/lib/active_record/connection_adapters/postgis/schema_statements.rb +++ b/lib/active_record/connection_adapters/postgis/schema_statements.rb @@ -12,11 +12,11 @@ def new_column_from_field(table_name, field, _definitions) type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) default_value = extract_value_from_default(default) - if attgenerated.present? - default_function = default - else - default_function = extract_default_function(default_value, default) - end + default_function = if attgenerated.present? + default + else + extract_default_function(default_value, default) + end if (match = default_function&.match(/\Anextval\('"?(?.+_(?seq\d*))"?'::regclass\)\z/)) serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name] @@ -25,8 +25,9 @@ def new_column_from_field(table_name, field, _definitions) # {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"} spatial = spatial_column_info(table_name).get(column_name, type_metadata.sql_type) - SpatialColumn.new( + Column.new( column_name, + get_oid_type(oid.to_i, fmod.to_i, column_name, type), default_value, type_metadata, !notnull, @@ -50,6 +51,11 @@ def spatial_column_info(table_name) @spatial_column_info ||= {} @spatial_column_info[table_name.to_sym] ||= SpatialColumnInfo.new(self, table_name.to_s) end + + # Returns an array of view names defined in the database. + def views + super - %w[geography_columns geometry_columns] + end end end end diff --git a/lib/active_record/connection_adapters/postgis_adapter.rb b/lib/active_record/connection_adapters/postgis_adapter.rb index 1a823e6e..1a749e4b 100644 --- a/lib/active_record/connection_adapters/postgis_adapter.rb +++ b/lib/active_record/connection_adapters/postgis_adapter.rb @@ -12,7 +12,7 @@ require_relative "postgis/database_statements" require_relative "postgis/spatial_column_info" require_relative "postgis/spatial_table_definition" -require_relative "postgis/spatial_column" +require_relative "postgis/column" require_relative "postgis/arel_tosql" require_relative "postgis/oid/spatial" require_relative "postgis/oid/date_time" @@ -84,7 +84,7 @@ def initialize_type_map(map = type_map) # "geometry(Polygon,4326) NOT NULL" # "geometry(Geography,4326)" geo_type, srid, has_z, has_m, geographic = PostGIS::OID::Spatial.parse_sql_type(sql_type) - PostGIS::OID::Spatial.new(geo_type: geo_type, srid: srid, has_z: has_z, has_m: has_m, geographic: geographic) + PostGIS::OID::Spatial.new(geo_type: geo_type, srid: srid, has_z: has_z, has_m: has_m, geographic: geographic).freeze end end @@ -92,9 +92,7 @@ def initialize_type_map(map = type_map) end def native_database_types - @native_database_types ||= begin - default_types = PostgreSQLAdapter.native_database_types - default_types.merge({ + @native_database_types ||= super.merge({ geography: { name: "geography" }, geometry: { name: "geometry" }, geometry_collection: { name: "geometry_collection" }, @@ -106,7 +104,6 @@ def native_database_types st_point: { name: "st_point" }, st_polygon: { name: "st_polygon" } }) - end end end diff --git a/test/database.yml b/test/database.yml index cda9c85b..47e439cd 100644 --- a/test/database.yml +++ b/test/database.yml @@ -3,14 +3,12 @@ connections: arunit: host: <%= ENV["PGHOST"] || "127.0.0.1" %> port: <%= ENV["PGPORT"] || "5432" %> - database: <%= ENV["PGDATABASE"] || "postgis_adapter_test" %> username: <%= ENV["PGUSER"] || "postgres" %> password: <%= ENV["PGPASSWORD"] || "" %> setup: default arunit2: host: <%= ENV["PGHOST"] || "127.0.0.1" %> port: <%= ENV["PGPORT"] || "5432" %> - database: <%= ENV["PGDATABASE"] || "postgis_adapter_test" %> username: <%= ENV["PGUSER"] || "postgres" %> password: <%= ENV["PGPASSWORD"] || "" %> setup: default @@ -19,7 +17,6 @@ connections: prepared_statements: false host: <%= ENV["PGHOST"] || "127.0.0.1" %> port: <%= ENV["PGPORT"] || "5432" %> - database: <%= ENV["PGDATABASE"] || "postgis_adapter_test" %> username: <%= ENV["PGUSER"] || "postgres" %> password: <%= ENV["PGPASSWORD"] || "" %> setup: default diff --git a/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb b/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb new file mode 100644 index 00000000..ed1f6040 --- /dev/null +++ b/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb @@ -0,0 +1,2 @@ +require_relative "fix_backtrace_cleaner" +include(FixBacktraceCleaner) diff --git a/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb b/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb new file mode 100644 index 00000000..ed1f6040 --- /dev/null +++ b/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb @@ -0,0 +1,2 @@ +require_relative "fix_backtrace_cleaner" +include(FixBacktraceCleaner) diff --git a/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb b/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb new file mode 100644 index 00000000..ed1f6040 --- /dev/null +++ b/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb @@ -0,0 +1,2 @@ +require_relative "fix_backtrace_cleaner" +include(FixBacktraceCleaner) diff --git a/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb b/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb new file mode 100644 index 00000000..ed1f6040 --- /dev/null +++ b/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb @@ -0,0 +1,2 @@ +require_relative "fix_backtrace_cleaner" +include(FixBacktraceCleaner) diff --git a/test/excludes/AssociationDeprecationTest/WarnModeTest.rb b/test/excludes/AssociationDeprecationTest/WarnModeTest.rb new file mode 100644 index 00000000..ed1f6040 --- /dev/null +++ b/test/excludes/AssociationDeprecationTest/WarnModeTest.rb @@ -0,0 +1,2 @@ +require_relative "fix_backtrace_cleaner" +include(FixBacktraceCleaner) diff --git a/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb b/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb new file mode 100644 index 00000000..9ed08029 --- /dev/null +++ b/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb @@ -0,0 +1,10 @@ +module FixBacktraceCleaner + def setup + super + bc = ActiveSupport::BacktraceCleaner.new + bc.remove_silencers! + bc.remove_filters! + bc.add_silencer { !_1.include?(::AssociationDeprecationTest::TestCase::THIS_FILE) } + ActiveRecord::LogSubscriber.backtrace_cleaner = bc + end +end diff --git a/test/excludes/CounterCacheTest.rb b/test/excludes/CounterCacheTest.rb deleted file mode 100644 index b8407c20..00000000 --- a/test/excludes/CounterCacheTest.rb +++ /dev/null @@ -1,55 +0,0 @@ -exclude "test_update_multiple_counters_with_touch:_%i(_updated_at_written_on_)", TRIAGE_MSG -exclude "test_reset_multiple_counters", TRIAGE_MSG -exclude "test_increment_counter", TRIAGE_MSG -exclude "test_decrement_counter_by_specific_amount", TRIAGE_MSG -exclude "test_update_counters_with_touch:_true", TRIAGE_MSG -exclude "test_update_counters_doesn't_touch_timestamps_by_default", TRIAGE_MSG -exclude "test_reset_counter_performs_query_for_correct_counter_with_touch:_true", TRIAGE_MSG -exclude "test_update_counters_doesn't_touch_timestamps_with_touch:_[]", TRIAGE_MSG -exclude "test_reset_multiple_counters_with_touch:_%i(_updated_at_written_on_)", TRIAGE_MSG -exclude "test_update_counter_for_decrement", TRIAGE_MSG -exclude "test_the_passed_symbol_needs_to_be_an_association_name_or_counter_name", TRIAGE_MSG -exclude "test_update_counter_for_decrement_for_cpk_model", TRIAGE_MSG -exclude "test_decrement_counter_for_cpk_model", TRIAGE_MSG -exclude "test_update_counters_with_touch:_:written_on", TRIAGE_MSG -exclude "test_reset_counters_with_modularized_and_camelized_classnames", TRIAGE_MSG -exclude "test_decrement_counters_with_touch:_true", TRIAGE_MSG -exclude "test_increment_counters_with_touch:_true", TRIAGE_MSG -exclude "test_decrement_counters_with_touch:_:written_on", TRIAGE_MSG -exclude "test_reset_multiple_counters_with_touch:_true", TRIAGE_MSG -exclude "test_reset_counters", TRIAGE_MSG -exclude "test_update_counters_in_a_polymorphic_relationship", TRIAGE_MSG -exclude "test_update_multiple_counters", TRIAGE_MSG -exclude "test_update_other_counters_on_parent_destroy", TRIAGE_MSG -exclude "test_reset_counters_with_string_argument", TRIAGE_MSG -exclude "test_reset_counter_with_belongs_to_which_has_class_name", TRIAGE_MSG -exclude "test_update_multiple_counters_with_touch:_true", TRIAGE_MSG -exclude "test_update_counters_of_multiple_records_with_touch:_true", TRIAGE_MSG -exclude "test_counter_caches_are_updated_in_memory_when_the_default_value_is_nil", TRIAGE_MSG -exclude "test_increment_counter_by_specific_amount", TRIAGE_MSG -exclude "test_decrement_counter", TRIAGE_MSG -exclude "test_reset_counters_with_touch:_true", TRIAGE_MSG -exclude "test_reset_the_right_counter_if_two_have_the_same_foreign_key", TRIAGE_MSG -exclude "test_inactive_counter_cache", TRIAGE_MSG -exclude "test_update_counters_of_multiple_records", TRIAGE_MSG -exclude "test_counters_are_updated_both_in_memory_and_in_the_database_on_create", TRIAGE_MSG -exclude "test_update_counter_with_initial_null_value", TRIAGE_MSG -exclude "test_decrement_counters_with_touch:_%i(_updated_at_written_on_)", TRIAGE_MSG -exclude "test_increment_counters_with_touch:_%i(_updated_at_written_on_)", TRIAGE_MSG -exclude "test_increment_counter_for_multiple_cpk_model_records", TRIAGE_MSG -exclude "test_reset_multiple_counters_with_touch:_:written_on", TRIAGE_MSG -exclude "test_reset_counters_with_touch:_%i(_updated_at_written_on_)", TRIAGE_MSG -exclude "test_reset_counters_by_counter_name", TRIAGE_MSG -exclude "test_reset_counter_skips_query_for_correct_counter", TRIAGE_MSG -exclude "test_increment_counter_for_cpk_model", TRIAGE_MSG -exclude "test_reset_counter_works_with_select_declared_on_association", TRIAGE_MSG -exclude "test_removing_association_updates_counter", TRIAGE_MSG -exclude "test_reset_counters_with_touch:_:written_on", TRIAGE_MSG -exclude "test_reset_counters_for_cpk_model", TRIAGE_MSG -exclude "test_reset_the_right_counter_if_two_have_the_same_class_name", TRIAGE_MSG -exclude "test_active_counter_cache", TRIAGE_MSG -exclude "test_increment_counters_with_touch:_:written_on", TRIAGE_MSG -exclude "test_update_multiple_counters_with_touch:_:written_on", TRIAGE_MSG -exclude "test_counter_cache_column?", TRIAGE_MSG -exclude "test_update_counters_with_touch:_%i(_updated_at_written_on_)", TRIAGE_MSG -exclude "test_reset_counter_of_has_many_:through_association", TRIAGE_MSG diff --git a/test/excludes/FinderTest.rb b/test/excludes/FinderTest.rb deleted file mode 100644 index e494cb9a..00000000 --- a/test/excludes/FinderTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude "test_find_by_on_attribute_that_is_a_reserved_word", TRIAGE_MSG diff --git a/test/excludes/MaterializedViewTest.rb b/test/excludes/MaterializedViewTest.rb deleted file mode 100644 index 64f72410..00000000 --- a/test/excludes/MaterializedViewTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude "test_views", TRIAGE_MSG diff --git a/test/excludes/MultiDbMigratorTest.rb b/test/excludes/MultiDbMigratorTest.rb deleted file mode 100644 index 2a06a9a7..00000000 --- a/test/excludes/MultiDbMigratorTest.rb +++ /dev/null @@ -1,4 +0,0 @@ -exclude :test_migrator_forward, "Hangs indefinitely. Doesn't care about timeout" -exclude :test_get_all_versions, "Hangs indefinitely. Doesn't care about timeout" -exclude :test_finds_pending_migrations, "Hangs indefinitely. Doesn't care about timeout" -exclude :test_migrator_db_has_no_schema_migrations_table, "Too slow, something's wrong." diff --git a/test/excludes/PreloaderTest.rb b/test/excludes/PreloaderTest.rb deleted file mode 100644 index 7409c1d4..00000000 --- a/test/excludes/PreloaderTest.rb +++ /dev/null @@ -1,45 +0,0 @@ -exclude "test_preload_with_grouping_sets_inverse_association", TRIAGE_MSG -exclude "test_preload_wont_set_the_wrong_target", TRIAGE_MSG -exclude "test_preload_with_available_records_queries_when_incomplete", TRIAGE_MSG -exclude "test_preload_does_not_group_same_class_different_scope", TRIAGE_MSG -exclude "test_preloads_belongs_to_a_composite_primary_key_model_through_id_attribute", TRIAGE_MSG -exclude "test_preload_with_available_records_queries_when_scoped", TRIAGE_MSG -exclude "test_preload_with_instance_dependent_through_scope", TRIAGE_MSG -exclude "test_preload_grouped_queries_of_middle_records", TRIAGE_MSG -exclude "test_preload_groups_queries_with_same_scope", TRIAGE_MSG -exclude "test_preload_with_unpersisted_records_no_ops", TRIAGE_MSG -exclude "test_preload_makes_correct_number_of_queries_on_relation", TRIAGE_MSG -exclude "test_preload_keeps_built_belongs_to_records_no_ops", TRIAGE_MSG -exclude "test_preload_does_not_group_same_scope_different_key_name", TRIAGE_MSG -exclude "test_some_already_loaded_associations", TRIAGE_MSG -exclude "test_preload_grouped_queries_of_through_records", TRIAGE_MSG -exclude "test_preload_with_scope", TRIAGE_MSG -exclude "test_preload_groups_queries_with_same_scope_at_second_level", TRIAGE_MSG -exclude "test_preload_does_not_concatenate_duplicate_records", TRIAGE_MSG -exclude "test_preload_with_available_records_with_through_association", TRIAGE_MSG -exclude "test_preload_with_some_records_already_loaded", TRIAGE_MSG -exclude "test_preload_grouped_queries_with_already_loaded_records", TRIAGE_MSG -exclude "test_preload_with_through_instance_dependent_scope", TRIAGE_MSG -exclude "test_preload_can_group_separate_levels", TRIAGE_MSG -exclude "test_preload_with_instance_dependent_scope", TRIAGE_MSG -exclude "test_preload_loaded_belongs_to_association_with_composite_foreign_key", TRIAGE_MSG -exclude "test_preload_for_hmt_with_conditions", TRIAGE_MSG -exclude "test_preload_has_many_through_association_with_composite_query_constraints", TRIAGE_MSG -exclude "test_preload_with_only_some_records_available", TRIAGE_MSG -exclude "test_preload_has_many_association_with_composite_foreign_key", TRIAGE_MSG -exclude "test_preload_can_group_multi_level_ping_pong_through", TRIAGE_MSG -exclude "test_preload_with_available_records_sti", TRIAGE_MSG -exclude "test_preload_through_records_with_already_loaded_middle_record", TRIAGE_MSG -exclude "test_preload_with_available_records", TRIAGE_MSG -exclude "test_preload_makes_correct_number_of_queries_on_array", TRIAGE_MSG -exclude "test_preload_groups_queries_with_same_sql_at_second_level", TRIAGE_MSG -exclude "test_preload_through", TRIAGE_MSG -exclude "test_preload_keeps_built_has_many_records_after_query", TRIAGE_MSG -exclude "test_preload_with_available_records_with_multiple_classes", TRIAGE_MSG -exclude "test_preload_with_available_records_queries_when_collection", TRIAGE_MSG -exclude "test_preload_with_only_some_records_available_with_through_associations", TRIAGE_MSG -exclude "test_multi_database_polymorphic_preload_with_same_table_name", TRIAGE_MSG -exclude "test_preload_keeps_built_belongs_to_records_after_query", TRIAGE_MSG -exclude "test_preloads_has_many_on_model_with_a_composite_primary_key_through_id_attribute", TRIAGE_MSG -exclude "test_preload_belongs_to_association_with_composite_foreign_key", TRIAGE_MSG -exclude "test_preload_keeps_built_has_many_records_no_ops", TRIAGE_MSG diff --git a/test/excludes/SameNameDifferentDatabaseFixturesTest.rb b/test/excludes/SameNameDifferentDatabaseFixturesTest.rb deleted file mode 100644 index f63f9c24..00000000 --- a/test/excludes/SameNameDifferentDatabaseFixturesTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude "test_fixtures_are_properly_loaded", TRIAGE_MSG diff --git a/test/excludes/SchemaDumperTest.rb b/test/excludes/SchemaDumperTest.rb index 28c51345..ae164cd9 100644 --- a/test/excludes/SchemaDumperTest.rb +++ b/test/excludes/SchemaDumperTest.rb @@ -1,5 +1,4 @@ exclude "test_schema_dump_with_timestamptz_datetime_format", TRIAGE_MSG -exclude "test_schema_dump_when_changing_datetime_type_for_an_existing_app", TRIAGE_MSG if ActiveRecord::Base.lease_connection.pool.server_version(ActiveRecord::Base.lease_connection) < 15_00_00 exclude "test_schema_dumps_unique_constraints", TRIAGE_MSG end diff --git a/test/excludes/SchemaIndexIncludeColumnsTest.rb b/test/excludes/SchemaIndexIncludeColumnsTest.rb deleted file mode 100644 index f7c836a8..00000000 --- a/test/excludes/SchemaIndexIncludeColumnsTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude "test_schema_dumps_index_included_columns", TRIAGE_MSG diff --git a/test/excludes/ViewWithPrimaryKeyTest.rb b/test/excludes/ViewWithPrimaryKeyTest.rb deleted file mode 100644 index 64f72410..00000000 --- a/test/excludes/ViewWithPrimaryKeyTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude "test_views", TRIAGE_MSG diff --git a/test/excludes/ViewWithoutPrimaryKeyTest.rb b/test/excludes/ViewWithoutPrimaryKeyTest.rb deleted file mode 100644 index 64f72410..00000000 --- a/test/excludes/ViewWithoutPrimaryKeyTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude "test_views", TRIAGE_MSG diff --git a/test/test_helper.rb b/test/test_helper.rb index 1a001c38..c55e8cf2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,66 +9,48 @@ require "erb" require "byebug" if ENV["BYEBUG"] require "activerecord-postgis-adapter" +require "timeout" TRIAGE_MSG = "Needs triage and fixes. See #378" -if ENV["ARCONN"] - # only install activerecord schema if we need it - require "cases/helper" +ENV["ARCONN"] ||= "postgis" - def load_postgis_specific_schema - original_stdout = $stdout - $stdout = StringIO.new - - load SCHEMA_ROOT + "/postgresql_specific_schema.rb" - - ActiveRecord::FixtureSet.reset_cache - ensure - $stdout = original_stdout - end +# We need to require this before the original `cases/helper` +# to make sure we patch load schema before it runs. +require "support/load_schema_helper" - load_postgis_specific_schema +module LoadSchemaHelperExt + # Postgis uses the postgresql specific schema. + # We need to explicit that behavior. + def load_postgis_specific_schema + # silence verbose schema loading + shh do + load SCHEMA_ROOT + "/postgresql_specific_schema.rb" - module ARTestCaseOverride - def with_postgresql_datetime_type(type) - adapter = ActiveRecord::ConnectionAdapters::PostGISAdapter - adapter.remove_instance_variable(:@native_database_types) if adapter.instance_variable_defined?(:@native_database_types) - datetime_type_was = adapter.datetime_type - adapter.datetime_type = type - yield - ensure - adapter = ActiveRecord::ConnectionAdapters::PostGISAdapter - adapter.datetime_type = datetime_type_was - adapter.remove_instance_variable(:@native_database_types) if adapter.instance_variable_defined?(:@native_database_types) + ActiveRecord::FixtureSet.reset_cache end end - ActiveRecord::TestCase.prepend(ARTestCaseOverride) -else - module ActiveRecord - class Base - DATABASE_CONFIG_PATH = __dir__ + "/database.yml" - - def self.test_connection_hash - conns = YAML.load(ERB.new(File.read(DATABASE_CONFIG_PATH)).result) - conn_hash = conns["connections"]["postgis"]["arunit"] - conn_hash.merge(adapter: "postgis") - end + def load_schema + super + load_postgis_specific_schema + end - def self.establish_test_connection - establish_connection test_connection_hash - end - end + private def shh + original_stdout = $stdout + $stdout = StringIO.new + yield + ensure + $stdout = original_stdout end +end +LoadSchemaHelper.prepend(LoadSchemaHelperExt) - ActiveRecord::Base.establish_test_connection -end # end if ENV["ARCONN"] +require "cases/helper" class SpatialModel < ActiveRecord::Base end -require 'timeout' - module TestTimeoutHelper def time_it t0 = Minitest.clock_time @@ -109,3 +91,43 @@ def reset_spatial_store end end end + +if ENV["JSON_REPORTER"] + puts "Generating JSON report: #{ENV["JSON_REPORTER"]}" + module Minitest + class JSONReporter < StatisticsReporter + def report + super + io.write( + { + seed: Minitest.seed, + assertions: assertions, + count: count, + failed_tests: results.reject(&:skipped?), # .failure.message + total_time: total_time, + failures: failures, + errors: errors, + warnings: warnings, + skips: skips, + }.to_json + ) + end + end + + def self.plugin_json_reporter_init(*) + reporter << JSONReporter.new(File.open(ENV["JSON_REPORTER"], "w")) + end + + self.load_plugins + self.extensions << "json_reporter" + end +end + +# Using '--fail-fast' may cause the rails plugin to raise Interrupt when recording +# a test. This would prevent other plugins from recording it. Hence we make sure +# that rails plugin is loaded last. +Minitest.load_plugins +if Minitest.extensions.include?("rails") + Minitest.extensions.delete("rails") + Minitest.extensions << "rails" +end