Add workflow to test seeding in all locales

There is a `script/i18n/test_seed_all_locales` to test seeding in all
locales, or one locale.

A GitHub action tests all locales seeding every week on Sunday at 2 AM
UTC. It can also be triggered manually.

Example:
    script/i18n/test_seed_all_locales         # Seed all locales sequentially
    script/i18n/test_seed_all_locales --list  # Output available locales as JSON
    script/i18n/test_seed_all_locales zh-CN   # Seed a single locale
This commit is contained in:
Christophe Bliard
2026-02-11 15:47:01 +01:00
parent 769d79c012
commit ae41e6a77d
2 changed files with 287 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
name: Test seeding in all locales
on:
schedule:
- cron: '0 2 * * 0' # Weekly on Sunday at 2 AM UTC
workflow_dispatch:
inputs:
ref:
description: 'Git ref to test (branch, tag, or SHA). Defaults to latest release branch.'
required: false
type: string
permissions:
contents: read
jobs:
prepare:
if: github.repository == 'opf/openproject'
name: Prepare
runs-on: ubuntu-latest
outputs:
locales: ${{ steps.list.outputs.locales }}
ref: ${{ steps.use_input_or_find_latest_release.outputs.ref }}
steps:
- name: Determine git ref to test seeding in
id: use_input_or_find_latest_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_REF: ${{ inputs.ref }}
run: |
if [ -n "$INPUT_REF" ]; then
echo "ref=$INPUT_REF" >> "$GITHUB_OUTPUT"
else
BRANCH=$(gh api repos/opf/openproject/branches --paginate --jq '.[].name' | grep '^release/' | sort --version-sort | tail -1)
if [ -z "$BRANCH" ]; then
echo "Error: no release branch found"
exit 1
fi
echo "Found latest release branch: $BRANCH"
echo "ref=$BRANCH" >> "$GITHUB_OUTPUT"
fi
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ steps.use_input_or_find_latest_release.outputs.ref }}
- name: List available locales
id: list
run: |
locales=$(ruby script/i18n/test_seed_all_locales --list)
echo "locales=$locales" >> "$GITHUB_OUTPUT"
seed:
needs: prepare
if: github.repository == 'opf/openproject'
name: Seed ${{ matrix.locale }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
locale: ${{ fromJson(needs.prepare.outputs.locales) }}
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/openproject_seed_test
RAILS_ENV: development
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.prepare.outputs.ref }}
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpq-dev
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Configure database
run: |
cat > config/database.yml <<'EOF'
development:
url: <%= ENV["DATABASE_URL"] %>
EOF
- name: Seed locale ${{ matrix.locale }}
run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }}
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# Tests that seeding works in all available locales.
#
# Usage:
# script/i18n/test_seed_all_locales # Seed all locales sequentially
# script/i18n/test_seed_all_locales --list # Output available locales as JSON
# script/i18n/test_seed_all_locales zh-CN # Seed a single locale
require "pathname"
require "json"
class SeedAllLocales
class << self
def call(args)
case args.first
when "-h", "--help"
print_usage
when "--list"
puts JSON.generate(available_locales)
when nil
seed_all_locales
else
locale = args.first
validate_locale!(locale)
seed_one_locale(locale)
end
end
private
def print_usage
puts <<~USAGE
Usage: #{$0} [OPTIONS] [LOCALE]
Tests that seeding works in all available locales.
Uses the current development database.
Options:
-h, --help Show this help message
--list Output available locales as JSON array
Arguments:
LOCALE Seed a single locale (e.g. zh-CN, de, en)
Examples:
#{$0} # Seed all locales sequentially
#{$0} --list # Output available locales as JSON
#{$0} zh-CN # Seed a single locale
USAGE
end
def validate_locale!(locale)
return if available_locales.include?(locale)
warn "Error: unknown locale '#{locale}'"
warn "Available locales: #{available_locales.join(', ')}"
exit 1
end
def available_locales
@available_locales ||=
rails_root.glob("config/locales/**/*.yml")
.map { |f| f.basename.to_s.split(".", 2).first }
.reject { |l| l.start_with?("js-") || l == "lol" }
.uniq
.sort
end
def rails_root
@rails_root ||=
Pathname.new(__dir__)
.ascend
.find { |dir| dir.join("Gemfile").exist? }
.tap { |dir| raise "Unable to find Rails root directory (looking up from #{__dir__})" if dir.nil? }
end
# Runs all locales sequentially and reports all failures at the end.
def seed_all_locales
locales = available_locales
puts "Testing seeding in #{locales.count} locales"
puts "Locales: #{locales.join(', ')}"
puts
unless setup_schema
puts "ERROR: Database schema setup failed. Cannot continue."
exit 1
end
results = {}
locales.each_with_index do |locale, index|
puts
puts "=== [#{index + 1}/#{locales.count}] Seeding locale: #{locale} ==="
success = reset_and_seed(locale)
results[locale] = success
status = success ? "OK" : "FAILED"
puts "--- #{locale}: #{status} ---"
end
print_summary(results)
end
# Runs a single locale and exits with appropriate status code.
def seed_one_locale(locale)
puts "=== Seeding locale: #{locale} ==="
terminate_db_connections
unless run("bin/rails", "db:drop", "db:create", "db:migrate")
puts "--- #{locale}: FAILED (database setup) ---"
exit 1
end
unless run("bin/rails", "db:seed", env: { "OPENPROJECT_SEED_LOCALE" => locale })
puts "--- #{locale}: FAILED (seeding) ---"
exit 1
end
puts "--- #{locale}: OK ---"
end
def setup_schema
puts "=== Setting up database schema ==="
terminate_db_connections
run("bin/rails", "db:drop", "db:create", "db:migrate", "db:schema:dump")
end
def reset_and_seed(locale)
terminate_db_connections
run("bin/rails", "db:drop", "db:create", "db:schema:load") &&
run("bin/rails", "db:seed", env: { "OPENPROJECT_SEED_LOCALE" => locale })
end
def terminate_db_connections
db_name = database_name
return unless db_name
run("psql", "-d", "postgres", "-c",
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity " \
"WHERE datname = '#{db_name}' AND pid <> pg_backend_pid()")
end
def database_name
@database_name ||= begin
require "uri"
db_url = ENV.fetch("DATABASE_URL", nil)
if db_url
URI.parse(db_url).path.delete_prefix("/")
else
# Fall back to asking Rails; use .split.last to ignore any
# extra output from initializers (e.g. REPL commands messages)
`bin/rails runner "print ActiveRecord::Base.connection_db_config.database"`.split.last
end
end
end
def run(*cmd, env: {})
env_str = env.map { |k, v| "#{k}=#{v}" }.join(" ")
puts " $ #{env_str} #{cmd.join(' ')}".strip
system(env, *cmd, chdir: rails_root.to_s)
end
def print_summary(results)
failed = results.reject { |_, success| success }
succeeded = results.select { |_, success| success }
puts <<~SUMMARY
================================
SUMMARY: #{succeeded.count} succeeded, #{failed.count} failed out of #{results.count} locales
================================
SUMMARY
if failed.any?
puts <<~FAILED
Failed locales:
#{failed.keys.map { |locale| " - #{locale}" }.join("\n")}
FAILED
exit 1
end
end
end
end
SeedAllLocales.call(ARGV)