From 364df12362fcb75fad818442f7138a4736947cb4 Mon Sep 17 00:00:00 2001 From: Mane Darbinyan Date: Tue, 7 Jan 2025 18:59:25 +0400 Subject: [PATCH] Add support for running tests against different DB adapters --- .github/workflows/main.yml | 6 +- README.md | 18 +++++ Rakefile | 2 + docker-compose.yml | 23 +++++++ docker/mysql-init/create_secondary_db.sql | 1 + docker/postgres-init/create_secondary_db.sql | 1 + .../patches/migration_context.rb | 11 ++- lib/tasks/test.rake | 69 +++++++++++++++++++ .../migrations_controller_test.rb | 25 ++++--- .../phantom_migrations_controller_test.rb | 25 ++++--- test/support/test_utils.rb | 10 ++- test/test_helper.rb | 61 ++++++++++++++++ 12 files changed, 227 insertions(+), 25 deletions(-) create mode 100644 docker-compose.yml create mode 100644 docker/mysql-init/create_secondary_db.sql create mode 100644 docker/postgres-init/create_secondary_db.sql create mode 100644 lib/tasks/test.rake diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d40121..99a0474 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,14 +26,14 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install SQLite3 Development Libraries - run: sudo apt-get update && sudo apt-get install -y libsqlite3-dev + run: sudo apt-get update && sudo apt-get install -y libsqlite3-dev docker-compose - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - name: Run the default task - run: bundle exec rake + - name: Run Tests with All Adapters + run: bundle exec rake test:all rubocop: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 489f9e9..f184d77 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,24 @@ To run tests with a specific version of Rails using Appraisal: bundle exec appraisal rails.6.0 rake test TEST=test/rake_task_test.rb TESTOPTS="--name=/db::db:rollback_branches#test_0003_keeps/" ``` +By default, `rake test` runs tests using `SQLite3`. To explicitly run tests with `SQLite3`, `PostgreSQL`, or `MySQL`, you can use the following tasks: +- Run tests with `SQLite3`: + ```sh + bundle exec rake test:sqlite3 + ``` +- Run tests with `PostgreSQL` (requires Docker): + ```sh + bundle exec rake test:postgresql + ``` +- Run tests with `MySQL` (requires Docker): + ```sh + bundle exec rake test:mysql2 + ``` +- Run tests for all supported adapters: + ```sh + bundle exec rake test:all + ``` + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/widefix/actual_db_schema. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/widefix/actual_db_schema/blob/master/CODE_OF_CONDUCT.md). diff --git a/Rakefile b/Rakefile index 3849cc4..33f8bd5 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,8 @@ require "bundler/gem_tasks" require "rake/testtask" +load "lib/tasks/test.rake" + Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f0d3fba --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + postgres: + image: postgres:14 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: actual_db_schema_test + ports: + - "5432:5432" + volumes: + - ./docker/postgres-init:/docker-entrypoint-initdb.d + + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: actual_db_schema_test + ports: + - "3306:3306" + volumes: + - ./docker/mysql-init:/docker-entrypoint-initdb.d diff --git a/docker/mysql-init/create_secondary_db.sql b/docker/mysql-init/create_secondary_db.sql new file mode 100644 index 0000000..ae5d676 --- /dev/null +++ b/docker/mysql-init/create_secondary_db.sql @@ -0,0 +1 @@ +CREATE DATABASE actual_db_schema_test_secondary; diff --git a/docker/postgres-init/create_secondary_db.sql b/docker/postgres-init/create_secondary_db.sql new file mode 100644 index 0000000..ae5d676 --- /dev/null +++ b/docker/postgres-init/create_secondary_db.sql @@ -0,0 +1 @@ +CREATE DATABASE actual_db_schema_test_secondary; diff --git a/lib/actual_db_schema/patches/migration_context.rb b/lib/actual_db_schema/patches/migration_context.rb index df35bad..85d879b 100644 --- a/lib/actual_db_schema/patches/migration_context.rb +++ b/lib/actual_db_schema/patches/migration_context.rb @@ -108,7 +108,7 @@ def handle_rollback_error(migration, exception) error_message = <<~ERROR Error encountered during rollback: - #{exception.message.gsub(/^An error has occurred, all later migrations canceled:\s*/, "").strip} + #{cleaned_exception_message(exception.message)} ERROR puts colorize(error_message, :red) @@ -118,6 +118,15 @@ def handle_rollback_error(migration, exception) branch: branch_for(migration.version.to_s) ) end + + def cleaned_exception_message(message) + patterns_to_remove = [ + /^An error has occurred, all later migrations canceled:\s*/, + /^An error has occurred, this and all later migrations canceled:\s*/ + ] + + patterns_to_remove.reduce(message.strip) { |msg, pattern| msg.gsub(pattern, "").strip } + end end end end diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake new file mode 100644 index 0000000..7f8be1d --- /dev/null +++ b/lib/tasks/test.rake @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +namespace :test do # rubocop:disable Metrics/BlockLength + desc "Run tests with SQLite3" + task :sqlite3 do + ENV["DB_ADAPTER"] = "sqlite3" + Rake::Task["test"].invoke + Rake::Task["test"].reenable + end + + desc "Run tests with PostgreSQL" + task :postgresql do + sh "docker-compose up -d postgres" + wait_for_postgres + + begin + ENV["DB_ADAPTER"] = "postgresql" + Rake::Task["test"].invoke + Rake::Task["test"].reenable + ensure + sh "docker-compose down" + end + end + + desc "Run tests with MySQL" + task :mysql2 do + sh "docker-compose up -d mysql" + wait_for_mysql + + begin + ENV["DB_ADAPTER"] = "mysql2" + Rake::Task["test"].invoke + Rake::Task["test"].reenable + ensure + sh "docker-compose down" + end + end + + desc "Run tests with all adapters (SQLite3, PostgreSQL, MySQL)" + task all: %i[sqlite3 postgresql mysql2] + + def wait_for_postgres + retries = 10 + begin + sh "docker-compose exec -T postgres pg_isready -U postgres" + rescue StandardError + retries -= 1 + + raise "MySQL is not ready after several attempts." if retries < 1 + + sleep 2 + retry + end + end + + def wait_for_mysql + retries = 10 + begin + sh "docker-compose exec -T mysql mysqladmin ping -h 127.0.0.1 --silent" + rescue StandardError + retries -= 1 + + raise "MySQL is not ready after several attempts." if retries < 1 + + sleep 2 + retry + end + end +end diff --git a/test/controllers/actual_db_schema/migrations_controller_test.rb b/test/controllers/actual_db_schema/migrations_controller_test.rb index 2b4288c..fe19a05 100644 --- a/test/controllers/actual_db_schema/migrations_controller_test.rb +++ b/test/controllers/actual_db_schema/migrations_controller_test.rb @@ -44,35 +44,35 @@ def active_record_setup assert_select "td", text: "20130906111511" assert_select "td", text: "FirstPrimary" assert_select "td", text: @utils.branch_for("20130906111511") - assert_select "td", text: "tmp/primary.sqlite3" + assert_select "td", text: @utils.primary_database end assert_select "tr" do assert_select "td", text: "up" assert_select "td", text: "20130906111512" assert_select "td", text: "SecondPrimary" assert_select "td", text: @utils.branch_for("20130906111512") - assert_select "td", text: "tmp/primary.sqlite3" + assert_select "td", text: @utils.primary_database end assert_select "tr" do assert_select "td", text: "up" assert_select "td", text: "20130906111514" assert_select "td", text: "FirstSecondary" assert_select "td", text: @utils.branch_for("20130906111514") - assert_select "td", text: "tmp/secondary.sqlite3" + assert_select "td", text: @utils.secondary_database end assert_select "tr" do assert_select "td", text: "up" assert_select "td", text: "20130906111515" assert_select "td", text: "SecondSecondary" assert_select "td", text: @utils.branch_for("20130906111515") - assert_select "td", text: "tmp/secondary.sqlite3" + assert_select "td", text: @utils.secondary_database end end end end test "GET #show returns a successful response" do - get :show, params: { id: "20130906111511", database: "tmp/primary.sqlite3" } + get :show, params: { id: "20130906111511", database: @utils.primary_database } assert_response :success assert_select "h2", text: "Migration FirstPrimary Details" assert_select "table" do @@ -86,7 +86,7 @@ def active_record_setup end assert_select "tr" do assert_select "th", text: "Database" - assert_select "td", text: "tmp/primary.sqlite3" + assert_select "td", text: @utils.primary_database end assert_select "tr" do assert_select "th", text: "Branch" @@ -96,7 +96,7 @@ def active_record_setup end test "GET #show returns a 404 response if migration not found" do - get :show, params: { id: "nil", database: "tmp/primary.sqlite3" } + get :show, params: { id: "nil", database: @utils.primary_database } assert_response :not_found end @@ -115,15 +115,20 @@ def down RUBY end @utils.prepare_phantom_migrations(TestingState.db_config) - post :rollback, params: { id: "20130906111513", database: "tmp/primary.sqlite3" } + post :rollback, params: { id: "20130906111513", database: @utils.primary_database } assert_response :redirect get :index - message = "An error has occurred, this and all later migrations canceled:\n\nActiveRecord::IrreversibleMigration" + message = if ENV["DB_ADAPTER"] == "mysql2" + "An error has occurred, all later migrations canceled:\n\nActiveRecord::IrreversibleMigration" + else + "An error has occurred, this and all later migrations canceled:\n\n" \ + "ActiveRecord::IrreversibleMigration" + end assert_select ".flash", text: message end test "POST #rollback changes migration status to down and hide migration with down status" do - post :rollback, params: { id: "20130906111511", database: "tmp/primary.sqlite3" } + post :rollback, params: { id: "20130906111511", database: @utils.primary_database } assert_response :redirect get :index assert_select "table" do diff --git a/test/controllers/actual_db_schema/phantom_migrations_controller_test.rb b/test/controllers/actual_db_schema/phantom_migrations_controller_test.rb index 7f9274d..47900ed 100644 --- a/test/controllers/actual_db_schema/phantom_migrations_controller_test.rb +++ b/test/controllers/actual_db_schema/phantom_migrations_controller_test.rb @@ -51,28 +51,28 @@ def active_record_setup assert_select "td", text: "20130906111511" assert_select "td", text: "FirstPrimary" assert_select "td", text: @utils.branch_for("20130906111511") - assert_select "td", text: "tmp/primary.sqlite3" + assert_select "td", text: @utils.primary_database end assert_select "tr" do assert_select "td", text: "up" assert_select "td", text: "20130906111512" assert_select "td", text: "SecondPrimary" assert_select "td", text: @utils.branch_for("20130906111512") - assert_select "td", text: "tmp/primary.sqlite3" + assert_select "td", text: @utils.primary_database end assert_select "tr" do assert_select "td", text: "up" assert_select "td", text: "20130906111514" assert_select "td", text: "FirstSecondary" assert_select "td", text: @utils.branch_for("20130906111514") - assert_select "td", text: "tmp/secondary.sqlite3" + assert_select "td", text: @utils.secondary_database end assert_select "tr" do assert_select "td", text: "up" assert_select "td", text: "20130906111515" assert_select "td", text: "SecondSecondary" assert_select "td", text: @utils.branch_for("20130906111515") - assert_select "td", text: "tmp/secondary.sqlite3" + assert_select "td", text: @utils.secondary_database end end end @@ -86,7 +86,7 @@ def active_record_setup end test "GET #show returns a successful response" do - get :show, params: { id: "20130906111511", database: "tmp/primary.sqlite3" } + get :show, params: { id: "20130906111511", database: @utils.primary_database } assert_response :success assert_select "h2", text: "Phantom Migration FirstPrimary Details" assert_select "table" do @@ -100,7 +100,7 @@ def active_record_setup end assert_select "tr" do assert_select "th", text: "Database" - assert_select "td", text: "tmp/primary.sqlite3" + assert_select "td", text: @utils.primary_database end assert_select "tr" do assert_select "th", text: "Branch" @@ -110,12 +110,12 @@ def active_record_setup end test "GET #show returns a 404 response if migration not found" do - get :show, params: { id: "nil", database: "tmp/primary.sqlite3" } + get :show, params: { id: "nil", database: @utils.primary_database } assert_response :not_found end test "POST #rollback changes migration status to down and hide migration with down status" do - post :rollback, params: { id: "20130906111511", database: "tmp/primary.sqlite3" } + post :rollback, params: { id: "20130906111511", database: @utils.primary_database } assert_response :redirect get :index assert_select "table" do @@ -151,10 +151,15 @@ def down RUBY end @utils.prepare_phantom_migrations(TestingState.db_config) - post :rollback, params: { id: "20130906111513", database: "tmp/primary.sqlite3" } + post :rollback, params: { id: "20130906111513", database: @utils.primary_database } assert_response :redirect get :index - message = "An error has occurred, this and all later migrations canceled:\n\nActiveRecord::IrreversibleMigration" + message = if ENV["DB_ADAPTER"] == "mysql2" + "An error has occurred, all later migrations canceled:\n\nActiveRecord::IrreversibleMigration" + else + "An error has occurred, this and all later migrations canceled:\n\n" \ + "ActiveRecord::IrreversibleMigration" + end assert_select ".flash", text: message end diff --git a/test/support/test_utils.rb b/test/support/test_utils.rb index 30203cc..1f945d1 100644 --- a/test/support/test_utils.rb +++ b/test/support/test_utils.rb @@ -150,6 +150,14 @@ def define_acronym(acronym) end end + def primary_database + TestingState.db_config["primary"]["database"] + end + + def secondary_database + TestingState.db_config["secondary"]["database"] + end + private def cleanup_call(prefix_name = nil) @@ -192,7 +200,7 @@ def clear_schema_call def applied_migrations_call run_sql("select * from schema_migrations").map do |row| - row["version"] + row.is_a?(Hash) ? row["version"] : row[0] end end diff --git a/test/test_helper.rb b/test/test_helper.rb index e79a785..4c3d901 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -32,6 +32,21 @@ def self.reset end def self.db_config + adapter = ENV["DB_ADAPTER"] || "sqlite3" + + case adapter + when "sqlite3" + sqlite3_config + when "postgresql" + postgresql_config + when "mysql2" + mysql2_config + else + raise "Unsupported adapter: #{adapter}" + end + end + + def self.sqlite3_config { "primary" => { "adapter" => "sqlite3", @@ -46,6 +61,52 @@ def self.db_config } end + def self.postgresql_config + { + "primary" => { + "adapter" => "postgresql", + "database" => "actual_db_schema_test", + "username" => "postgres", + "password" => "password", + "host" => "localhost", + "port" => 5432, + "migrations_paths" => Rails.root.join("db", "migrate").to_s + }, + "secondary" => { + "adapter" => "postgresql", + "database" => "actual_db_schema_test_secondary", + "username" => "postgres", + "password" => "password", + "host" => "localhost", + "port" => 5432, + "migrations_paths" => Rails.root.join("db", "migrate_secondary").to_s + } + } + end + + def self.mysql2_config + { + "primary" => { + "adapter" => "mysql2", + "database" => "actual_db_schema_test", + "username" => "root", + "password" => "password", + "host" => "127.0.0.1", + "port" => "3306", + "migrations_paths" => Rails.root.join("db", "migrate").to_s + }, + "secondary" => { + "adapter" => "mysql2", + "database" => "actual_db_schema_test_secondary", + "username" => "root", + "password" => "password", + "host" => "127.0.0.1", + "port" => "3306", + "migrations_paths" => Rails.root.join("db", "migrate_secondary").to_s + } + } + end + reset end