diff --git a/.github/workflows/flaky.yml b/.github/workflows/flaky.yml index 71d48bc6..2e4a4ef3 100644 --- a/.github/workflows/flaky.yml +++ b/.github/workflows/flaky.yml @@ -33,15 +33,16 @@ jobs: ruby=$(jq --raw-input --compact-output 'split(" ")' <<<"${{ github.event.inputs.ruby }}") crdb_len=$(wc -w <<<"${{ github.event.inputs.crdb }}") ruby_len=$(wc -w <<<"${{ github.event.inputs.ruby }}") - (( range_count = ${{github.event.inputs.max}} / ( crdb_len * ruby_len ) )) - range=$(jq --compact-output "[range($range_count)]" <<<[]) + (( seeds_count = ${{github.event.inputs.max}} / ( crdb_len * ruby_len ) )) + seeds=$(shuf --input-range=1-65535 --head-count=$seeds_count | jq --slurp --compact-output) + echo $seeds echo "crdb=$crdb" >> $GITHUB_OUTPUT echo "ruby=$ruby" >> $GITHUB_OUTPUT - echo "numbers=$range" >> $GITHUB_OUTPUT + echo "seeds=$seeds" >> $GITHUB_OUTPUT outputs: crdb: ${{ steps.generate-matrix.outputs.crdb }} ruby: ${{ steps.generate-matrix.outputs.ruby }} - numbers: ${{ steps.generate-matrix.outputs.numbers }} + seeds: ${{ steps.generate-matrix.outputs.seeds }} test: runs-on: ubuntu-latest needs: prepare-matrix @@ -50,13 +51,23 @@ jobs: matrix: crdb: ${{ fromJSON(needs.prepare-matrix.outputs.crdb) }} ruby: ${{ fromJSON(needs.prepare-matrix.outputs.ruby) }} - number: ${{ fromJSON(needs.prepare-matrix.outputs.numbers) }} - name: Test (crdb=${{ matrix.crdb }} ruby=${{ matrix.ruby }} number=${{ matrix.number }}) + seed: ${{ fromJSON(needs.prepare-matrix.outputs.seeds) }} + name: Test (crdb=${{ matrix.crdb }} ruby=${{ matrix.ruby }} seed=${{ matrix.seed }}) + env: + SEED: ${{ matrix.seed }} + COCKROACH_LOAD_FROM_TEMPLATE: yes_please steps: - name: Set Up Actions uses: actions/checkout@v4 - uses: ./.github/actions/test-runner + id: test with: crdb: ${{ matrix.crdb }} ruby: ${{ matrix.ruby }} TESTOPTS: --fail-fast + - name: Bisect failing test + if: ${{ failure() && steps.test.conclusion == 'failure' }} + run: bin/minitest_bisect ${{ matrix.seed }} + env: + MTB_VERBOSE: 2 + MTB_DEBUG: 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58f8bf71..b7ccd3aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,23 +61,16 @@ Only do it if you know the schema was left in a correct state. ### Run Tests from a Backup -Loading the full test schema every time a test runs can take a while, so for cases where loading the schema sequentially is unimportant, it is possible to use a backup to set up the database. This is significantly faster than the standard method and is provided to run individual tests faster, but should not be used to validate a build. - -First create the template database. - -```bash -bundle exec rake db:create_test_template -``` - -This will create a template database for the current version (ex. `activerecord_test_template611` for version 6.1.1) and create a `BACKUP` in the `nodelocal://self/activerecord-crdb-adapter/#{activerecord_version}` directory. - -To load from the template, use the `COCKROACH_LOAD_FROM_TEMPLATE` flag. - -```bash -COCKROACH_LOAD_FROM_TEMPLATE=1 TEST_FILES="test/cases/adapters/postgresql/ddl_test.rb" bundle exec rake test -``` - -And the `activerecord_unittest` database will use the `RESTORE` command to load the schema from the template database. +Loading the full test schema every time a test runs can take +a while, so for cases where loading the schema sequentially +is unimportant, it is possible to use a backup to set up the +database. This is significantly faster than the standard +method and is provided to run individual tests faster, but +should not be used to validate a build. + +To do so, just set the env variable `COCKROACH_LOAD_FROM_TEMPLATE`. +First run will generate and cache a template, latter runs will use +it. # Improvements diff --git a/Gemfile b/Gemfile index 903ce903..276f253c 100644 --- a/Gemfile +++ b/Gemfile @@ -54,6 +54,7 @@ group :development, :test do gem "rake" gem "debug" + gem "minitest-bisect", github: "BuonOmo/minitest-bisect", branch: "no-fail-fast" gem "minitest-excludes", "~> 2.0.1" gem "minitest-github_action_reporter", github: "BuonOmo/minitest-github_action_reporter", require: "minitest/github_action_reporter_plugin" gem "ostruct", "~> 0.6" diff --git a/Rakefile b/Rakefile index 21437647..f7243da5 100644 --- a/Rakefile +++ b/Rakefile @@ -2,28 +2,9 @@ require "bundler/gem_tasks" require "rake/testtask" require_relative 'test/support/paths_cockroachdb' require_relative 'test/support/rake_helpers' -require_relative 'test/support/template_creator' task default: [:test] -namespace :db do - task "create_test_template" do - ENV['DEBUG_COCKROACHDB_ADAPTER'] = "1" - ENV['COCKROACH_SKIP_LOAD_SCHEMA'] = "1" - - TemplateCreator.connect - require_relative 'test/cases/helper' - - # TODO: look into this more, but for some reason the blob alias - # is not defined while running this task. - ActiveRecord::ConnectionAdapters::CockroachDB::TableDefinition.class_eval do - alias :blob :binary - end - - TemplateCreator.create_test_template - end -end - Rake::TestTask.new do |t| t.libs = ARTest::CockroachDB.test_load_paths t.test_files = RakeHelpers.test_files diff --git a/activerecord-cockroachdb-adapter.gemspec b/activerecord-cockroachdb-adapter.gemspec index 9492cb0e..542d058b 100644 --- a/activerecord-cockroachdb-adapter.gemspec +++ b/activerecord-cockroachdb-adapter.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps." spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter" - spec.add_dependency "activerecord", "~> 8.0.0" + spec.add_dependency "activerecord", "~> 8.1.0.a" spec.add_dependency "pg", "~> 1.5" spec.add_dependency "rgeo-activerecord", "~> 8.0.0" diff --git a/bin/minitest_bisect b/bin/minitest_bisect new file mode 100755 index 00000000..d3fef657 --- /dev/null +++ b/bin/minitest_bisect @@ -0,0 +1,19 @@ +#!/usr/bin/env -S bundle exec ruby + +require 'rake/file_list' +require_relative '../test/support/paths_cockroachdb' +require_relative '../test/support/rake_helpers' + +libs = ARTest::CockroachDB.test_load_paths +test_files = RakeHelpers.test_files + +Dir.chdir(File.dirname __dir__) do + system( + "bundle", + "exec", + "minitest_bisect", + "--seed=#{ARGV[0]}", + "-I" + libs.join(":"), + *test_files + ) +end diff --git a/lib/active_record/connection_adapters/cockroachdb/column.rb b/lib/active_record/connection_adapters/cockroachdb/column.rb index fdaab7c7..eee40652 100644 --- a/lib/active_record/connection_adapters/cockroachdb/column.rb +++ b/lib/active_record/connection_adapters/cockroachdb/column.rb @@ -20,7 +20,7 @@ module CockroachDB class Column < PostgreSQL::Column # most functions taken from activerecord-postgis-adapter spatial_column # https://github.com/rgeo/activerecord-postgis-adapter/blob/master/lib/active_record/connection_adapters/postgis/spatial_column.rb - 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, identity: nil, serial: nil, spatial: nil, generated: nil, hidden: nil) @sql_type_metadata = sql_type_metadata @@ -45,7 +45,7 @@ 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, + super(name, cast_type, 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) } diff --git a/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb b/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb index b951e4f6..c5abc93e 100644 --- a/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +++ b/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb @@ -74,7 +74,7 @@ def structure_load(filename, extra_flags=nil) "https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/new" end - run_cmd("cockroach", ["sql", "--set", "errexit=false", "--file", filename], "loading") + run_cmd("cockroach", "sql", "--set", "errexit=false", "--file", filename) end private diff --git a/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb b/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb index eed4ffc7..740b8421 100644 --- a/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +++ b/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb @@ -28,6 +28,10 @@ class Spatial < Type::Value def initialize(oid, sql_type) @sql_type = sql_type @geo_type, @srid, @has_z, @has_m = self.class.parse_sql_type(sql_type) + @spatial_factory = + RGeo::ActiveRecord::SpatialFactoryStore.instance.factory( + factory_attrs + ) end # sql_type: geometry, geometry(Point), geometry(Point,4326), ... @@ -59,13 +63,6 @@ def self.parse_sql_type(sql_type) [geo_type, srid, has_z, has_m] end - def spatial_factory - @spatial_factory ||= - RGeo::ActiveRecord::SpatialFactoryStore.instance.factory( - factory_attrs - ) - end - def geographic? @sql_type =~ /geography/ end @@ -108,14 +105,14 @@ def parse_wkt(string) end def binary_string?(string) - string[0] == "\x00" || string[0] == "\x01" || string[0, 4] =~ /[0-9a-fA-F]{4}/ + string[0] == "\x00" || string[0] == "\x01" || string[0, 4].match?(/[0-9a-fA-F]{4}/) end def wkt_parser(string) if binary_string?(string) - RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid) + RGeo::WKRep::WKBParser.new(@spatial_factory, support_ewkb: true, default_srid: @srid) else - RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid) + RGeo::WKRep::WKTParser.new(@spatial_factory, support_ewkt: true, default_srid: @srid) end end diff --git a/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb b/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb index ba5f469a..a7500781 100644 --- a/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +++ b/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb @@ -250,6 +250,7 @@ def new_column_from_field(table_name, field, _definition) CockroachDB::Column.new( column_name, + get_oid_type(oid.to_i, fmod.to_i, column_name, type), default_value, type_metadata, !notnull, diff --git a/lib/active_record/connection_adapters/cockroachdb_adapter.rb b/lib/active_record/connection_adapters/cockroachdb_adapter.rb index 3a98cbff..489ba726 100644 --- a/lib/active_record/connection_adapters/cockroachdb_adapter.rb +++ b/lib/active_record/connection_adapters/cockroachdb_adapter.rb @@ -299,7 +299,7 @@ def initialize_type_map(m = type_map) st_polygon ).each do |geo_type| m.register_type(geo_type) do |oid, _, sql_type| - CockroachDB::OID::Spatial.new(oid, sql_type) + CockroachDB::OID::Spatial.new(oid, sql_type).freeze end end diff --git a/test/cases/adapter_test.rb b/test/cases/adapter_test.rb index 5d394fef..99535ed1 100644 --- a/test/cases/adapter_test.rb +++ b/test/cases/adapter_test.rb @@ -70,6 +70,7 @@ class AdapterForeignKeyTest < ActiveRecord::TestCase fixtures :fk_test_has_pk def before_setup + super conn = ActiveRecord::Base.lease_connection conn.drop_table :fk_test_has_fk, if_exists: true @@ -87,6 +88,7 @@ def before_setup end def setup + super @connection = ActiveRecord::Base.lease_connection end diff --git a/test/cases/adapters/postgresql/postgis_test.rb b/test/cases/adapters/postgresql/postgis_test.rb index f3dd811d..561cd10a 100644 --- a/test/cases/adapters/postgresql/postgis_test.rb +++ b/test/cases/adapters/postgresql/postgis_test.rb @@ -185,12 +185,6 @@ def reset_memoized_spatial_factories # necessary to reset the @spatial_factory variable on spatial # OIDs, otherwise the results of early tests will be memoized # since the table is not dropped and recreated between test cases. - ObjectSpace.each_object(spatial_oid) do |oid| - oid.instance_variable_set(:@spatial_factory, nil) - end - end - - def spatial_oid - ActiveRecord::ConnectionAdapters::CockroachDB::OID::Spatial + klass.lease_connection.send(:reload_type_map) end end diff --git a/test/cases/fixtures_test.rb b/test/cases/fixtures_test.rb index 86c44d62..5c714242 100644 --- a/test/cases/fixtures_test.rb +++ b/test/cases/fixtures_test.rb @@ -18,6 +18,29 @@ require "models/traffic_light" require "models/treasure" +# Hacky tool that searches for table definition code in schema.rb +# and evals it. Doing this ensure we're always using the latest +# schema. +module TableCreator + def self.create_table_using_test_schema(table_name, connection) + table_name = table_name.to_sym + @schema_file ||= SCHEMA_ROOT + "/schema.rb" + @ast ||= Prism::Translation::Parser.parse_file(@schema_file) + to_search = [@ast] + found = nil + while !to_search.empty? + node = to_search.shift + next unless node.is_a?(Parser::AST::Node) + if node in [:block, [:send, _, :create_table, [:sym, ^table_name], *], *] + break found = node + end + to_search += node.children + end + raise "Schema #{table_name.inspect} not found" unless found + connection.instance_eval(found.location.expression.source) + end +end + module CockroachDB class FixturesTest < ActiveRecord::TestCase include ConnectionHelper @@ -288,6 +311,7 @@ class FixturesResetPkSequenceTest < ActiveRecord::TestCase # We'll do this in a before_setup so we get ahead of # ActiveRecord::TestFixtures#before_setup. def before_setup + super Account.lease_connection.drop_table :accounts, if_exists: true Account.lease_connection.exec_query("CREATE SEQUENCE IF NOT EXISTS accounts_id_seq") Account.lease_connection.exec_query(" @@ -336,53 +360,33 @@ def before_setup end def setup + super @instances = [Account.new(credit_limit: 50), Company.new(name: "RoR Consulting"), Course.new(name: "Test")] ActiveRecord::FixtureSet.reset_cache # make sure tables get reinitialized end # Drop the primary key sequences and bring back the original tables. def teardown + super Account.lease_connection.drop_table :accounts, if_exists: true Account.lease_connection.exec_query("DROP SEQUENCE IF EXISTS accounts_id_seq") - Account.lease_connection.create_table :accounts, force: true do |t| - t.timestamps null: true - t.references :firm, index: false - t.string :firm_name - t.integer :credit_limit - t.string :status - t.integer "a" * max_identifier_length - end + TableCreator.create_table_using_test_schema(:accounts, Account.lease_connection) Company.lease_connection.drop_table :companies, if_exists: true Company.lease_connection.exec_query("DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE") - Company.lease_connection.create_table :companies, force: true do |t| - t.string :type - t.references :firm, index: false - t.string :firm_name - t.string :name - t.bigint :client_of - t.bigint :rating, default: 1 - t.integer :account_id - t.string :description, default: "" - t.integer :status, default: 0 - t.index [:name, :rating], order: :desc - t.index [:name, :description], length: 10 - t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc } - t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)" - t.index [:firm_id], name: "company_nulls_not_distinct", nulls_not_distinct: true - t.index :name, name: "company_name_index", using: :btree - t.index "(CASE WHEN rating > 0 THEN lower(name) END) DESC", name: "company_expression_index" if Company.lease_connection.supports_expression_index? - t.index [:firm_id, :type], name: "company_include_index", include: [:name, :account_id] - end + TableCreator.create_table_using_test_schema(:companies, Company.lease_connection) + # CockroachDB specific schema addition + Company.lease_connection.add_index(:companies, [:firm_id, :type], name: "company_include_index", include: [:name, :account_id]) Course.lease_connection.drop_table :courses, if_exists: true Course.lease_connection.exec_query("DROP SEQUENCE IF EXISTS courses_id_seq") - Course.lease_connection.create_table :courses, force: true do |t| - t.column :name, :string, null: false - t.column :college_id, :integer, index: true - end + TableCreator.create_table_using_test_schema(:courses, Course.lease_connection) recreate_parrots + + Account.reset_column_information + Company.reset_column_information + Course.reset_column_information end # This replaces the same test that's been excluded from @@ -448,28 +452,9 @@ def recreate_parrots conn.drop_table :parrots_treasures, if_exists: true conn.drop_table :parrots, if_exists: true - conn.create_table :parrots, force: :cascade do |t| - t.string :name - t.integer :breed, default: 0 - t.string :color - t.string :parrot_sti_class - t.integer :killer_id - t.integer :updated_count, :integer, default: 0 - t.datetime :created_at - t.datetime :created_on - t.datetime :updated_at - t.datetime :updated_on - end - - conn.create_table :parrots_pirates, id: false, force: true do |t| - t.references :parrot, foreign_key: true - t.references :pirate, foreign_key: true - end - - conn.create_table :parrots_treasures, id: false, force: true do |t| - t.references :parrot, foreign_key: true - t.references :treasure, foreign_key: true - end + TableCreator.create_table_using_test_schema(:parrots, conn) + TableCreator.create_table_using_test_schema(:parrots_pirates, conn) + TableCreator.create_table_using_test_schema(:parrots_treasures, conn) end end end diff --git a/test/cases/helper_cockroachdb.rb b/test/cases/helper_cockroachdb.rb index 1f453420..48ce5e0e 100644 --- a/test/cases/helper_cockroachdb.rb +++ b/test/cases/helper_cockroachdb.rb @@ -7,7 +7,6 @@ module ExcludeMessage end require "minitest/excludes" -require "minitest/github_action_reporter" # This gives great visibility on schema dump related tests, but # some rails specific messages are then ignored. @@ -23,11 +22,54 @@ module ExcludeMessage require "support/load_schema_helper" module LoadSchemaHelperExt + # Load the CockroachDB specific schema. It replaces ActiveRecord's PostgreSQL + # specific schema. + def load_cockroachdb_specific_schema + # silence verbose schema loading + shh do + load "schema/cockroachdb_specific_schema.rb" + + ActiveRecord::FixtureSet.reset_cache + end + end + def load_schema - return if ENV['COCKROACH_LOAD_FROM_TEMPLATE'] return if ENV['COCKROACH_SKIP_LOAD_SCHEMA'] + unless ENV['COCKROACH_LOAD_FROM_TEMPLATE'] + print "Loading schema..." + t0 = Time.now + super + load_cockroachdb_specific_schema + puts format(" %.2fs", Time.now - t0) + end + + require 'support/template_creator' + + if TemplateCreator.template_exists? + print "Loading schema from template..." + else + print "Generating and caching template schema..." + end - super + t0 = Time.now + + TemplateCreator.load_from_template do + super + load_cockroachdb_specific_schema + end + + puts format(" %.2fs", Time.now - t0) + + # reconnect to activerecord_unittest + shh { ARTest.connect } + end + + private def shh + original_stdout = $stdout + $stdout = StringIO.new + yield + ensure + $stdout = original_stdout end end LoadSchemaHelper.prepend(LoadSchemaHelperExt) @@ -42,34 +84,6 @@ def load_schema # Allow exclusion of tests by name using #exclude_from_transactional_tests(test_name) ActiveRecord::TestCase.prepend(ExcludeFromTransactionalTests) -# Load the CockroachDB specific schema. It replaces ActiveRecord's PostgreSQL -# specific schema. -def load_cockroachdb_specific_schema - # silence verbose schema loading - original_stdout = $stdout - $stdout = StringIO.new - - load "schema/cockroachdb_specific_schema.rb" - - ActiveRecord::FixtureSet.reset_cache -ensure - $stdout = original_stdout -end - -if ENV['COCKROACH_LOAD_FROM_TEMPLATE'].nil? && ENV['COCKROACH_SKIP_LOAD_SCHEMA'].nil? - load_cockroachdb_specific_schema -elsif ENV['COCKROACH_LOAD_FROM_TEMPLATE'] - require 'support/template_creator' - - p "loading schema from template" - - # load from template - TemplateCreator.restore_from_template - - # reconnect to activerecord_unittest - ARTest.connect -end - require 'timeout' module TestTimeoutHelper @@ -152,21 +166,34 @@ def with_postgresql_datetime_type(type) ActiveRecord::TestCase.prepend(SetDatetimeInCockroachDBAdapter) -module Minitest - module GithubActionReporterExt - def gh_link(loc) - return super unless loc.include?("/gems/") +if ENV["GITHUB_ACTIONS"] + require "minitest/github_action_reporter" - path, _, line = loc[%r(/(?:test|spec|lib)/.*)][1..].rpartition(":") + module Minitest + module GithubActionReporterExt + def gh_link(loc) + return super unless loc.include?("/gems/") - rails_version = "v#{ActiveRecord::VERSION::STRING}" - "#{ENV["GITHUB_SERVER_URL"]}/rails/rails/blob/#{rails_version}/activerecord/#{path}#L#{line}" - rescue - warn "Failed to generate link for #{loc}" - super + path, _, line = loc[%r(/(?:test|spec|lib)/.*)][1..].rpartition(":") + + rails_version = "v#{ActiveRecord::VERSION::STRING}" + "#{ENV["GITHUB_SERVER_URL"]}/rails/rails/blob/#{rails_version}/activerecord/#{path}#L#{line}" + rescue + warn "Failed to generate link for #{loc}" + super + end end + GithubActionReporter.prepend(GithubActionReporterExt) end - GithubActionReporter.prepend(GithubActionReporterExt) +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 if ENV['TRACE_LIB'] 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/support/copy_cat.rb b/test/support/copy_cat.rb index cd9f0c73..30803fc5 100644 --- a/test/support/copy_cat.rb +++ b/test/support/copy_cat.rb @@ -20,7 +20,7 @@ def warn(message, category: nil, **kwargs) # Copy methods from The original rails class to our adapter. # While copying, we can rewrite the source code of the method using - # ast. Use `debug: true` to lead you true that process. + # ast. Use `debug: true` to lead you through that process. def copy_methods(new_klass, old_klass, *methods, debug: false, &block) methods.each do |met| file, _ = old_klass.instance_method(met).source_location diff --git a/test/support/exclude_from_transactional_tests.rb b/test/support/exclude_from_transactional_tests.rb index 76da602a..61775b64 100644 --- a/test/support/exclude_from_transactional_tests.rb +++ b/test/support/exclude_from_transactional_tests.rb @@ -18,7 +18,6 @@ def self.prepended(base) end def before_setup - # binding.irb if self.class.non_transactional_list.include?(@NAME.to_s) @old_use_transactional_tests = self.use_transactional_tests if @old_use_transactional_tests # stay false if false self.use_transactional_tests = !self.class.non_transactional_list.include?(@NAME.to_s) diff --git a/test/support/rake_helpers.rb b/test/support/rake_helpers.rb index e73c1b8e..6e573a2a 100644 --- a/test/support/rake_helpers.rb +++ b/test/support/rake_helpers.rb @@ -26,12 +26,12 @@ def test_files def all_test_files activerecord_test_files = - FileList["#{ARTest::CockroachDB.root_activerecord}/test/cases/**/*_test.rb"]. + ::Rake::FileList["#{ARTest::CockroachDB.root_activerecord}/test/cases/**/*_test.rb"]. reject { _1.include?("/adapters/") || _1.include?("/encryption/performance") } + - FileList["#{ARTest::CockroachDB.root_activerecord}/test/cases/adapters/postgresql/**/*_test.rb"] + ::Rake::FileList["#{ARTest::CockroachDB.root_activerecord}/test/cases/adapters/postgresql/**/*_test.rb"] - cockroachdb_test_files = FileList['test/cases/**/*_test.rb'] + cockroachdb_test_files = ::Rake::FileList['test/cases/**/*_test.rb'] - FileList[COCKROACHDB_TEST_HELPER] + activerecord_test_files + cockroachdb_test_files + ::Rake::FileList[COCKROACHDB_TEST_HELPER] + activerecord_test_files + cockroachdb_test_files end end diff --git a/test/support/template_creator.rb b/test/support/template_creator.rb index ef946d49..3437202f 100644 --- a/test/support/template_creator.rb +++ b/test/support/template_creator.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'active_record' +require "activerecord-cockroachdb-adapter" require_relative 'paths_cockroachdb' +require 'support/config' # ARTest.config +require 'support/connection' # ARTest.connect module TemplateCreator - # extend self - DEFAULT_CONNECTION_HASH = { adapter: 'cockroachdb', database: 'defaultdb', @@ -14,37 +14,60 @@ module TemplateCreator host: 'localhost' }.freeze - BACKUP_DIR = "nodelocal://self/activerecord-crdb-adapter" + BACKUP_DIR = "userfile://defaultdb.public/activerecord-crdb-adapter" module_function - def ar_version - ActiveRecord.version.version.gsub('.','') + def template_version + ar_version = ActiveRecord.version.version.gsub('.','_') + main_schema_digest = Digest::MD5.file(SCHEMA_ROOT + "/schema.rb").hexdigest + crdb_schema_digest = Digest::MD5.file("#{__dir__}/../schema/cockroachdb_specific_schema.rb").hexdigest + "#{ar_version}_#{main_schema_digest}_#{crdb_schema_digest}" end def version_backup_path - BACKUP_DIR + "/#{ar_version}" + BACKUP_DIR + "/#{template_version}" + end + + def template_db_name(db_name) + "#{db_name}__template__#{template_version}" + end + + # NOTE: this verification that databases exist may not be enough if a failure + # occurs during the backup process. However, a developer should see that + # failure happening, and in new envs we always run at least a first time + # the backup process. + def template_exists? + databases.all? { template_db_exists?(_1) } + end + + def connect + ActiveRecord::Base.establish_connection(DEFAULT_CONNECTION_HASH) end - def template_db_name - "activerecord_unittest_template#{ar_version}" + def databases + @databases ||=ARTest.config.dig("connections", "cockroachdb").map { |_, value| value["database"] }.uniq end - def connect(connection_hash=nil) - connection_hash = DEFAULT_CONNECTION_HASH if connection_hash.nil? - ActiveRecord::Base.establish_connection(connection_hash) + def with_template_db_names + old_crdb = ARTest.config["connections"]["cockroachdb"].dup + new_crdb = old_crdb.transform_values { |value| value.merge("database" => template_db_name(value["database"])) } + ARTest.config["connections"]["cockroachdb"] = new_crdb + yield + ensure + ARTest.config["connections"]["cockroachdb"] = old_crdb end - def template_db_exists? - ActiveRecord::Base.lease_connection.select_value("SELECT 1 FROM pg_database WHERE datname='#{template_db_name}'") == 1 + def template_db_exists?(db_name) + ActiveRecord::Base.lease_connection.select_value("SELECT 1 FROM pg_database WHERE datname='#{template_db_name(db_name)}'") == 1 end - def drop_template_db - ActiveRecord::Base.lease_connection.execute("DROP DATABASE #{template_db_name}") + def drop_template_db(db_name) + ActiveRecord::Base.lease_connection.execute("DROP DATABASE #{template_db_name(db_name)} CASCADE") end - def create_template_db - ActiveRecord::Base.lease_connection.execute("CREATE DATABASE #{template_db_name}") + def create_template_db(db_name) + ActiveRecord::Base.lease_connection.execute("CREATE DATABASE #{template_db_name(db_name)}") end def load_schema @@ -53,36 +76,50 @@ def load_schema load 'test/schema/cockroachdb_specific_schema.rb' end - def create_test_template + def create_test_template(&block) connect - raise "#{template_db_name} already exists. If you do not have a backup created, please drop the database and run again." if template_db_exists? - - create_template_db - - # switch connection to template db - conn = DEFAULT_CONNECTION_HASH.dup - conn['database'] = template_db_name - connect(conn) + databases.each do |db_name| + drop_template_db(db_name) if template_db_exists?(db_name) + create_template_db(db_name) + end - load_schema + with_template_db_names do + shh { ARTest.connect } + block.call + end - # create BACKUP to restore from - ActiveRecord::Base.lease_connection.execute("BACKUP DATABASE #{template_db_name} TO '#{version_backup_path}'") + connect + ActiveRecord::Base.lease_connection.execute(<<~SQL) + BACKUP DATABASE #{databases.map { |db| template_db_name(db) }.join(', ')} + INTO '#{version_backup_path}' + SQL end - def restore_from_template + def load_from_template(&block) connect - raise "The TemplateDB does not exist. Run 'rake db:create_test_template' first." unless template_db_exists? - - begin - ActiveRecord::Base.lease_connection.execute("DROP DATABASE activerecord_unittest") - rescue ActiveRecord::StatementInvalid => e - unless e.cause.class == PG::InvalidCatalogName - raise e + create_test_template(&block) unless template_exists? + databases.each do |db_name| + begin + ActiveRecord::Base.lease_connection.execute("DROP DATABASE #{db_name}") + rescue ActiveRecord::StatementInvalid => e + unless e.cause.class == PG::InvalidCatalogName + raise e + end end + ActiveRecord::Base.lease_connection.execute("CREATE DATABASE #{db_name}") + ActiveRecord::Base.lease_connection.execute(<<~SQL) + RESTORE #{template_db_name(db_name)}.* + FROM LATEST IN '#{version_backup_path}' + WITH into_db = '#{db_name}' + SQL end - ActiveRecord::Base.lease_connection.execute("CREATE DATABASE activerecord_unittest") + end - ActiveRecord::Base.lease_connection.execute("RESTORE #{template_db_name}.* FROM '#{version_backup_path}' WITH into_db = 'activerecord_unittest'") + private def shh + original_stdout = $stdout + $stdout = StringIO.new + yield + ensure + $stdout = original_stdout end end