Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
.loadpath
.project
.ruby-version
.mutant
doc
Gemfile.lock
Gemfile-custom
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ gem "database_cleaner", "~> 2.0", require: false
gem "rspec-activemodel-mocks", "~> 1.1", require: false
gem "rspec-rails", "~> 6.0.3", require: false
gem "rspec-retry", "~> 0.6.2", require: false
gem "mutant", require: false
gem "mutant-rspec", require: false
gem "simplecov", require: false
gem "simplecov-cobertura", require: false
gem "rack", "< 3", require: false
Expand Down
10 changes: 10 additions & 0 deletions bin/db-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Source this to point the test suite (and mutant) at the local PG container:
# source db-env.sh
#
# Matches bin/postgres (image postgres:18, user postgres / password password,
# 127.0.0.1:5432). DB is load-bearing for `bundle exec` since the Gemfile picks
# DB gems from $DB.
export DB=postgresql
export DB_HOST=127.0.0.1
export DB_USERNAME=postgres
export DB_PASSWORD=password
50 changes: 50 additions & 0 deletions bin/postgres
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
#
# Boot the PostgreSQL container used for mutant runs against solidus_core.
# Boots with user "postgres" / password "password".
#
# Fails loudly if the container is already running, so we never silently attach
# to (or clobber) an existing database.

set -euo pipefail

CONTAINER_NAME="postgres"
IMAGE="docker.io/library/postgres:18"
PG_USER="postgres"
PG_PASSWORD="password"

red() { printf '\033[1;31m%s\033[0m\n' "$*"; }
green(){ printf '\033[1;32m%s\033[0m\n' "$*"; }

# --- Complain loudly if it's already running --------------------------------
if podman container inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
status="$(podman container inspect -f '{{.State.Status}}' "$CONTAINER_NAME")"
if [ "$status" = "running" ]; then
red "############################################################"
red "## ALREADY RUNNING: container '$CONTAINER_NAME' is up."
red "## Refusing to start a second one."
red "##"
red "## Stop it first with:"
red "## podman stop $CONTAINER_NAME && podman rm $CONTAINER_NAME"
red "############################################################"
exit 1
fi

# Exists but stopped: remove the stale container so we start clean.
echo "Removing stopped container '$CONTAINER_NAME'..."
podman rm "$CONTAINER_NAME" >/dev/null
fi

# --- Start it ---------------------------------------------------------------
green "Starting '$CONTAINER_NAME' ($IMAGE) on 127.0.0.1:5432 ..."
podman run -d \
--name "$CONTAINER_NAME" \
-e POSTGRES_USER="$PG_USER" \
-e POSTGRES_PASSWORD="$PG_PASSWORD" \
-p "127.0.0.1:5432:5432" \
"$IMAGE" >/dev/null

green "Started. Connect with:"
cat <<EOF
DB=postgresql DB_HOST=127.0.0.1 DB_USERNAME=$PG_USER DB_PASSWORD=$PG_PASSWORD
EOF
22 changes: 22 additions & 0 deletions core/config/mutant.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
# Run from core/:
# bundle exec mutant environment show
# bundle exec mutant environment subject list Spree*
usage: opensource
includes:
- spec
requires:
- rails_helper
environment_variables:
RAILS_ENV: test
DB: postgresql
DB_HOST: 127.0.0.1
DB_USERNAME: postgres
DB_PASSWORD: password
integration:
name: rspec
hooks:
- spec/mutant_hooks.rb
matcher:
subjects:
- Spree*
94 changes: 94 additions & 0 deletions core/spec/mutant_hooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

# Mutant hooks (see core/.mutant.yml).
#
# Per the mutant Rails guide (docs/rails.md), parallel workers must each get
# their own database or they corrupt each other's fixtures. This is the
# PostgreSQL pattern: each worker clones the migrated test database via
# `CREATE DATABASE ... TEMPLATE`, named "<db>_mutant_worker_<index>".
#
# The guide's `with_root_connection` uses `ActiveRecord::Base.postgresql_connection`,
# which was removed in Rails 8.x; we open the maintenance connection with the pg
# gem directly instead. The rest follows the guide.
require "pg"

# Eager load so subjects are discoverable (Zeitwerk lazy-loads otherwise).
hooks.register(:env_infection_post) do
Rails.application.eager_load!
end

# Disconnect the parent before workers clone the template database.
hooks.register(:setup_integration_post) do
base_records.each do |base|
disconnect_pool(base:)
end
end

# Both registrations are required to isolate in both modes:
# mutation_worker_process_start for `mutant run`, test_worker_process_start for `mutant test`.
hooks.register(:test_worker_process_start) { |index:| isolate_index(index:) }

Check failure on line 29 in core/spec/mutant_hooks.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Layout/ExtraSpacing: Unnecessary spacing detected.
hooks.register(:mutation_worker_process_start) { |index:| isolate_index(index:) }

def self.base_records
[
ActiveRecord::Base,

Check failure on line 34 in core/spec/mutant_hooks.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Style/TrailingCommaInArrayLiteral: Avoid comma after the last item of an array.
]
end

def self.isolate_index(index:)
base_records.each do |base|
disconnect_pool(base:)
isolate_database(base:, index:)
end
end

def self.isolate_database(base:, index:)
db_config = base
.connection_handler
.retrieve_connection_pool(base.connection_specification_name)
.db_config

raw_template_database = db_config.database
raw_isolated_database = "#{raw_template_database}_mutant_worker_#{index}"

with_root_connection do |connection|
template_database = PG::Connection.quote_ident(raw_template_database)
isolated_database = PG::Connection.quote_ident(raw_isolated_database)

connection.exec("DROP DATABASE IF EXISTS #{isolated_database}")
connection.exec("CREATE DATABASE #{isolated_database} TEMPLATE #{template_database}")
end

db_config._database = raw_isolated_database
end

def self.disconnect_pool(base:)
base
.connection_handler
.retrieve_connection_pool(base.connection_specification_name)
.disconnect
end

# Open a connection to the "postgres" maintenance database so we can issue
# CREATE/DROP DATABASE. (Replaces the guide's removed Base.postgresql_connection.)
def self.with_root_connection
base = ActiveRecord::Base

config = base
.connection_handler
.retrieve_connection_pool(base.connection_specification_name)
.db_config
.configuration_hash

connection = PG.connect(
host: config[:host],

Check failure on line 84 in core/spec/mutant_hooks.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.
port: config[:port] || 5432,

Check failure on line 85 in core/spec/mutant_hooks.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.
user: config[:username],

Check failure on line 86 in core/spec/mutant_hooks.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.
password: config[:password],
dbname: "postgres"

Check failure on line 88 in core/spec/mutant_hooks.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.
)

yield connection
ensure
connection&.close
end
Loading