diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f839620f..d1bad500 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -73,6 +73,16 @@ createuser --superuser postgres DB=postgres bundle exec appraisal rails-5.2 rake ``` +## The dummy_app + +In the rare event you need a `console` in the `dummy_app`: + +``` +cd spec/dummy_app +cp config/database.mysql.yml config/database.yml +BUNDLE_GEMFILE='../../gemfiles/rails_5.2.gemfile' bin/rails console -e test +``` + ## Adding new schema Edit `spec/dummy_app/db/migrate/20110208155312_set_up_test_tables.rb`. Migration diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..f702a414 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,158 @@ +name: gha-workflow-pt-test +on: [push, pull_request] +jobs: + + # Linting is a separate job, primary because it only needs to be done once, + # and secondarily because jobs are performed concurrently. + gha-job-pt-lint: + name: Lint + runs-on: ubuntu-18.04 + steps: + - name: Checkout source + uses: actions/checkout@v2 + - name: Setup ruby + uses: actions/setup-ruby@v1 + with: + # Set to `TargetRubyVersion` in `.rubocopy.yml` + ruby-version: 2.4 + - name: Bundle + run: | + gem install bundler + bundle install --jobs 4 --retry 3 + - name: Lint + run: bundle exec rubocop + + # The test job is a matrix of ruby/rails versions. + gha-job-pt-test: + name: Ruby ${{ matrix.ruby }}, ${{ matrix.gemfile }}.gemfile + runs-on: ubuntu-18.04 + services: + gha-service-pt-mysql: + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: paper_trail_test + image: mysql:8.0 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + ports: + - 3306:3306 + gha-service-pt-postgres: + env: + POSTGRES_PASSWORD: asdfasdf + image: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + strategy: + # Unlike TravisCI, the database will not be part of the matrix. Each + # sub-job in the matrix tests all three databases. Alternatively, we could + # have set this up with each database as a separate job, but then we'd be + # duplicating the matrix configuration three times. + matrix: + gemfile: [ 'rails_5.2', 'rails_6.0', 'rails_6.1' ] + + # To keep matrix size down, only test highest and lowest rubies. In + # `.rubocopy.yml`, set `TargetRubyVersion`, to the lowest ruby version + # tested here. + ruby: [ '2.4', '2.7' ] + + exclude: + # rails 6 requires ruby >= 2.5.0 + - ruby: '2.4' + gemfile: 'rails_6.0' + - ruby: '2.4' + gemfile: 'rails_6.1' + steps: + - name: Checkout source + uses: actions/checkout@v2 + - name: Setup ruby + uses: actions/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Test connection to postgres, print lists of dbs, users + run: | + psql \ + --command '\l' \ + --command '\du' \ + --host=$POSTGRES_HOST \ + --port=$POSTGRES_PORT \ + --username=postgres \ + postgres + env: + PGPASSWORD: asdfasdf + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + - name: Test connection to mysql + run: | + mysql \ + --execute='show databases;' \ + --host=$MYSQL_HOST \ + --protocol=TCP \ + --port=$MYSQL_PORT \ + --user=$MYSQL_USER \ + $MYSQL_DATABASE + env: + MYSQL_DATABASE: paper_trail_test + MYSQL_HOST: localhost + MYSQL_PORT: 3306 + MYSQL_USER: root + - name: Bundle + run: | + gem install bundler + bundle install --jobs 4 --retry 3 + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile + + # MySQL db was created above, sqlite will be created during test suite, + # when migrations occur, so we only need to create the postgres db. I + # tried something like `cd .....dummy_app && ....db:create`, but couldn't + # get that to work. + - name: Create postgres database + run: | + createdb \ + --host=$POSTGRES_HOST \ + --port=$POSTGRES_PORT \ + --username=postgres \ + paper_trail_test + env: + PGPASSWORD: asdfasdf + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + + # The following three steps finally run the tests. We use `rake + # install_database_yml spec` instead of `rake` (default) because the + # default includes rubocop, which we run (once) as a separate job. See + # above. + - name: Test, sqlite + run: bundle exec rake install_database_yml spec + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile + DB: sqlite + - name: Test, mysql + run: bundle exec rake install_database_yml spec + env: + BACKTRACE: 1 + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile + DB: mysql + PT_CI_DATABASE: paper_trail + PT_CI_DB_USER: root + PT_CI_DB_HOST: 127.0.0.1 + PT_CI_DB_PORT: 3306 + - name: Test, postgres + run: bundle exec rake install_database_yml spec + env: + BACKTRACE: 1 + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile + DB: postgres + PT_CI_DATABASE: paper_trail + PT_CI_DB_USER: postgres + PT_CI_DB_PASSWORD: asdfasdf + PT_CI_DB_HOST: 127.0.0.1 + PT_CI_DB_PORT: 5432 diff --git a/.rspec b/.rspec index 83e16f80..2b70b733 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ +--backtrace --color --require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml index 3417aef0..6578d71b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -18,7 +18,7 @@ AllCops: # Enable pending cops so we can adopt the code before they are switched on. NewCops: enable - # Set to lowest supported version + # Set to lowest supported version of ruby TargetRubyVersion: 2.4 Bundler/OrderedGems: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 386f3560..00000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: ruby -cache: bundler - -# For ruby, we test the highest and lowest minor versions only. -rvm: - - 2.4 - - 2.7 - -env: - global: - - TRAVIS=true - matrix: - - DB=mysql - - DB=postgres - - DB=sqlite - -# Travis recommend we use the VM infrastructure (`sudo: required`) -sudo: required - -before_install: - - gem update bundler - -gemfile: - - gemfiles/rails_5.2.gemfile - - gemfiles/rails_6.0.gemfile - - gemfiles/rails_6.1.gemfile -matrix: - exclude: - # rails 6 requires ruby >= 2.5.0 - - rvm: 2.4 - gemfile: gemfiles/rails_6.0.gemfile - - rvm: 2.4 - gemfile: gemfiles/rails_6.1.gemfile - fast_finish: true -services: - - mysql - - postgresql diff --git a/Rakefile b/Rakefile index ba1eab5a..3f814c5f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,24 +1,36 @@ # frozen_string_literal: true +ENV["DB"] ||= "sqlite" + require "fileutils" require "bundler" Bundler::GemHelper.install_tasks -desc "Delete generated files and databases" -task :clean do +desc "Copy the database.DB.yml per ENV['DB']" +task :install_database_yml do + puts format("installing database.yml for %s", ENV["DB"]) + # It's tempting to use `git clean` here, but this rake task will be run by # people working on changes that haven't been committed yet, so we have to # be more selective with what we delete. ::FileUtils.rm("spec/dummy_app/db/database.yml", force: true) + + FileUtils.cp( + "spec/dummy_app/config/database.#{ENV['DB']}.yml", + "spec/dummy_app/config/database.yml" + ) +end + +desc "Delete generated files and databases" +task :clean do + puts format("dropping %s database", ENV["DB"]) case ENV["DB"] when "mysql" - %w[test foo bar].each do |db| - system("mysqladmin drop -f paper_trail_#{db} > /dev/null 2>&1") - end + # TODO: only works locally. doesn't respect database.yml + system "mysqladmin drop -f paper_trail_test > /dev/null 2>&1" when "postgres" - %w[test foo bar].each do |db| - system("dropdb --if-exists paper_trail_#{db} > /dev/null 2>&1") - end + # TODO: only works locally. doesn't respect database.yml + system "dropdb --if-exists paper_trail_test > /dev/null 2>&1" when nil, "sqlite" ::FileUtils.rm(::Dir.glob("spec/dummy_app/db/*.sqlite3")) else @@ -26,28 +38,21 @@ task :clean do end end -desc "Write a database.yml for the specified RDBMS" -task prepare: [:clean] do - ENV["DB"] ||= "sqlite" - FileUtils.cp( - "spec/dummy_app/config/database.#{ENV['DB']}.yml", - "spec/dummy_app/config/database.yml" - ) +desc <<~EOS + Write a database.yml for the specified RDBMS, and create database. Does not + migrate. Migration happens later in spec_helper. +EOS +task prepare: %i[clean install_database_yml] do + puts format("creating %s database", ENV["DB"]) case ENV["DB"] when "mysql" - %w[test foo bar].each do |db| - system("mysqladmin create paper_trail_#{db}") - # Migration happens later in spec_helper. - end + # TODO: only works locally. doesn't respect database.yml + system "mysqladmin create paper_trail_test" when "postgres" - %w[test foo bar].each do |db| - system("createdb paper_trail_#{db}") - # Migration happens later in spec_helper. - end + # TODO: only works locally. doesn't respect database.yml + system "createdb paper_trail_test" when nil, "sqlite" - # noop. test.sqlite3 will be created when migration happens in spec_helper. - # Shortly thereafter, foo and bar.sqlite3 are created when - # spec/support/alt_db_init.rb is `require`d. + # noop. test.sqlite3 will be created when migration happens nil else raise "Don't know how to create specified DB: #{ENV['DB']}" diff --git a/spec/dummy_app/bin/rails b/spec/dummy_app/bin/rails new file mode 100755 index 00000000..22f2d8de --- /dev/null +++ b/spec/dummy_app/bin/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/spec/dummy_app/config/database.mysql.yml b/spec/dummy_app/config/database.mysql.yml index d136e2dc..71936b3c 100644 --- a/spec/dummy_app/config/database.mysql.yml +++ b/spec/dummy_app/config/database.mysql.yml @@ -1,19 +1,12 @@ test: &test adapter: mysql2 encoding: utf8 - database: paper_trail_test + database: <%= ENV.fetch('PT_CI_DATABASE', 'paper_trail') %>_test pool: 5 - username: root + username: <%= ENV.fetch('PT_CI_DB_USER', 'root') %> + host: <%= ENV.fetch('PT_CI_DB_HOST', 'localhost') %> + port: <%= ENV.fetch('PT_CI_DB_PORT', 3306) %> + protocol: TCP + + # password deliberately blank password: - host: localhost - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -foo: - <<: *test - database: paper_trail_foo - -bar: - <<: *test - database: paper_trail_bar diff --git a/spec/dummy_app/config/database.postgres.yml b/spec/dummy_app/config/database.postgres.yml index 22e813d4..f9282ea1 100644 --- a/spec/dummy_app/config/database.postgres.yml +++ b/spec/dummy_app/config/database.postgres.yml @@ -1,15 +1,8 @@ test: &test adapter: postgresql - database: paper_trail_test - username: postgres - password: - host: localhost - port: 5432 - -foo: - <<: *test - database: paper_trail_foo - -bar: - <<: *test - database: paper_trail_bar + database: <%= ENV.fetch('PT_CI_DATABASE', 'paper_trail') %>_test + username: <%= ENV.fetch('PT_CI_DB_USER', 'postgres') %> + password: <%= ENV.fetch('PT_CI_DB_PASSWORD', '') %> + host: <%= ENV.fetch('PT_CI_DB_HOST', 'localhost') %> + port: <%= ENV.fetch('PT_CI_DB_PORT', 5432) %> + protocol: TCP diff --git a/spec/dummy_app/config/database.sqlite.yml b/spec/dummy_app/config/database.sqlite.yml index 25a6c64a..30984f8b 100644 --- a/spec/dummy_app/config/database.sqlite.yml +++ b/spec/dummy_app/config/database.sqlite.yml @@ -5,11 +5,3 @@ test: &test pool: 5 timeout: 5000 database: db/test.sqlite3 - -foo: - <<: *test - database: db/test-foo.sqlite3 - -bar: - <<: *test - database: db/test-bar.sqlite3 diff --git a/spec/paper_trail/version_concern_spec.rb b/spec/paper_trail/version_concern_spec.rb deleted file mode 100644 index 2e8db936..00000000 --- a/spec/paper_trail/version_concern_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" -require "support/alt_db_init" - -RSpec.describe PaperTrail::VersionConcern do - it "allows included class to have different connections" do - expect(Foo::Version.connection).not_to eq(Bar::Version.connection) - end - - it "allows custom version class to share connection with superclass" do - expect(Foo::Version.connection).to eq(Foo::Document.connection) - expect(Bar::Version.connection).to eq(Bar::Document.connection) - end - - it "can be used with class_name option" do - expect(Foo::Document.version_class_name).to eq("Foo::Version") - expect(Bar::Document.version_class_name).to eq("Bar::Version") - end - - describe "persistence", versioning: true do - it "stores versions in the correct corresponding db location" do - foo_doc = Foo::Document.create!(name: "foobar") - bar_doc = Bar::Document.create!(name: "raboof") - expect(foo_doc.versions.first).to be_instance_of(Foo::Version) - expect(bar_doc.versions.first).to be_instance_of(Bar::Version) - end - end -end diff --git a/spec/support/alt_db_init.rb b/spec/support/alt_db_init.rb deleted file mode 100644 index 6051d3b1..00000000 --- a/spec/support/alt_db_init.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require_relative "paper_trail_spec_migrator" - -# This file copies the test database into locations for the `Foo` and `Bar` -# namespace, then defines those namespaces, then establishes the sqlite3 -# connection for the namespaces to simulate an application with multiple -# database connections. - -# This is all going to change in rails 6. See "RailsConf 2018: Keynote: The -# Future of Rails 6: Scalable by Default by Eileen Uchitelle" -# https://www.youtube.com/watch?v=8evXWvM4oXM - -# Load database yaml to use -configs = YAML.load_file("#{Rails.root}/config/database.yml") - -# If we are testing with sqlite make it quick -db_directory = "#{Rails.root}/db" - -# Set up alternate databases -if ENV["DB"] == "sqlite" - FileUtils.cp "#{db_directory}/test.sqlite3", "#{db_directory}/test-foo.sqlite3" - FileUtils.cp "#{db_directory}/test.sqlite3", "#{db_directory}/test-bar.sqlite3" -end - -module Foo - class Base < ActiveRecord::Base - self.abstract_class = true - end - - class Version < Base - include PaperTrail::VersionConcern - end - - class Document < Base - has_paper_trail versions: { class_name: "Foo::Version" } - end -end - -Foo::Base.configurations = configs -Foo::Base.establish_connection(:foo) -ActiveRecord::Base.establish_connection(:foo) -::PaperTrailSpecMigrator.new.migrate - -module Bar - class Base < ActiveRecord::Base - self.abstract_class = true - end - - class Version < Base - include PaperTrail::VersionConcern - end - - class Document < Base - has_paper_trail versions: { class_name: "Bar::Version" } - end -end - -Bar::Base.configurations = configs -Bar::Base.establish_connection(:bar) -ActiveRecord::Base.establish_connection(:bar) -::PaperTrailSpecMigrator.new.migrate