discourse/.github/workflows/tests.yml
Alan Guo Xiang Tan 56ac103547
DEV: Copy gems from Docker image in Github actions tests workflow
On our own self hosted runners, pulling from github actions cache is quite slow and can take up to 20 seconds for the `bundler cache` step. Instead, we will now copy the gems from the `/var/www/discourse/vendor/bundle` folder in the `discourse/discourse_test:release` image which is built daily. This eliminates the need to pull from Github actions cache and also potentially reduces our costs of cache storage.

Note that `discourse/discourse_test:release` is pulled periodically on our self hosted runners so there is no impact on the time it takes to initialize the container.
2024-12-27 08:31:01 +08:00

441 lines
17 KiB
YAML

name: Tests
on:
pull_request:
paths-ignore:
- ".github/workflows/migration-tests.yml"
- ".github/dependabot.yml"
- ".github/labeler.yml"
- "migrations/**"
push:
branches:
- main
- beta
- stable
paths-ignore:
- ".github/workflows/migration-tests.yml"
- ".github/dependabot.yml"
- ".github/labeler.yml"
- "migrations/**"
concurrency:
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: ${{ matrix.target }} ${{ matrix.build_type }} # Update fetch-job-id step if changing this
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-22.04-8core') || 'debian-12' }}
container: discourse/discourse_test:${{ (github.repository == 'discourse/discourse' && 'release') || format('slim{0}', (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '') }}
timeout-minutes: 20
env:
DISCOURSE_HOSTNAME: www.example.com
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' || matrix.build_type == 'system' }}
CAPYBARA_DEFAULT_MAX_WAIT_TIME: 10
MINIO_RUNNER_LOG_LEVEL: DEBUG
DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && '1' }}
CHEAP_SOURCE_MAPS: "1"
TESTEM_DEFAULT_BROWSER: Chrome
MINIO_RUNNER_INSTALL_DIR: /home/discourse/.minio_runner
strategy:
fail-fast: false
matrix:
build_type: [backend, frontend, system, annotations]
target: [core, plugins, themes]
exclude:
- build_type: annotations
target: plugins
- build_type: annotations
target: themes
- build_type: backend
target: themes
- build_type: frontend
target: core # Handled by core_frontend_tests job (below)
include:
- build_type: system
target: chat
steps:
- name: Set working directory owner
run: chown root:root .
- name: Set PARALLEL_TEST_PROCESSORS for system tests
if: matrix.build_type == 'system'
run: |
echo "PARALLEL_TEST_PROCESSORS=$(($(nproc) / 2))" >> $GITHUB_ENV
- name: Set QUNIT_PARALLEL for QUnit tests
if: matrix.build_type == 'frontend'
run: |
if [ "${{ matrix.target }}" = "themes" ]; then
echo "QUNIT_PARALLEL=2" >> $GITHUB_ENV
else
echo "QUNIT_PARALLEL=$(($(nproc) / 2))" >> $GITHUB_ENV
fi
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: Start redis
run: |
redis-server /etc/redis/redis.conf &
- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Container envs
id: container-envs
run: |
echo "ruby_version=$RUBY_VERSION" >> $GITHUB_OUTPUT
echo "debian_release=$DEBIAN_RELEASE" >> $GITHUB_OUTPUT
shell: bash
- name: Bundler cache
uses: actions/cache@v4
if: github.repository != 'discourse/discourse'
with:
path: vendor/bundle
key: ${{ runner.os }}-${{ steps.container-envs.outputs.ruby_version }}-${{ steps.container-envs.outputs.debian_release }}-gem-${{ hashFiles('**/Gemfile.lock') }}-cachev2
- name: Copy gems to vendor/bundle
if: github.repository == 'discourse/discourse'
run: cp -r /var/www/discourse/vendor/bundle vendor/bundle
- name: Setup gems
run: |
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs $(($(nproc) - 1))
bundle clean
- name: pnpm install
run: pnpm install --frozen-lockfile
- name: Checkout official plugins
if: matrix.target == 'plugins'
run: bin/rake plugin:install_all_official
- name: Pull compatible versions of plugins
if: matrix.target == 'plugins' && (github.ref_name != 'main' && github.base_ref != 'main')
run: bin/rake plugin:pull_compatible_all
- name: Plugin gems cache
uses: actions/cache@v4
with:
path: plugins/*/gems
key: ${{ runner.os }}-plugin-gems-${{ steps.container-envs.outputs.ruby_version }}-${{ steps.container-envs.outputs.debian_release }}-${{ hashFiles('plugins/*/plugin.rb') }}
- name: Checkout official themes
if: matrix.target == 'themes'
run: bin/rake themes:clone_all_official themes:pull_compatible_all
- name: Add hosts to /etc/hosts, otherwise Chrome cannot reach minio
if: matrix.build_type == 'system' && matrix.target == 'core'
run: |
echo "127.0.0.1 minio.local" | sudo tee -a /etc/hosts
echo "127.0.0.1 discoursetest.minio.local" | sudo tee -a /etc/hosts
- name: Get CPU cores
id: cpu-info
run: echo "cpu-cores=$(nproc)" >> $GITHUB_OUTPUT
- name: Fetch app state cache
uses: actions/cache@v4
id: app-cache
with:
path: tmp/app-cache
key: >-
${{ runner.os }}-
${{ steps.cpu-info.outputs.cpu-cores }}-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
${{ hashFiles('config/environments/test.rb') }}-
${{ env.USES_PARALLEL_DATABASES }}-
${{ env.PARALLEL_TEST_PROCESSORS }}-
- name: Restore database from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: script/silence_successful_output psql --quiet -o /dev/null -f tmp/app-cache/cache.sql postgres
- name: Restore uploads from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
- name: Create and migrate database
if: steps.app-cache.outputs.cache-hit != 'true'
run: |
bin/rake db:create
script/silence_successful_output bin/rake db:migrate
- name: Create and migrate parallel databases
if: >-
env.USES_PARALLEL_DATABASES == 'true' &&
steps.app-cache.outputs.cache-hit != 'true'
run: |
bin/rake parallel:create
script/silence_successful_output bin/rake parallel:migrate
- name: Dump database for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql
- name: Dump uploads for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
- name: Fetch turbo_rspec_runtime.log cache
uses: actions/cache@v4
id: test-runtime-cache
if: matrix.build_type == 'backend' || matrix.build_type == 'system'
with:
path: tmp/turbo_rspec_runtime.log
key: rspec-runtime-${{ matrix.build_type }}-${{ matrix.target }}-${{ github.run_id }}
restore-keys: rspec-runtime-${{ matrix.build_type }}-${{ matrix.target }}-
- name: Check Zeitwerk reloading
if: matrix.build_type == 'backend'
env:
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
run: |
if ! bin/rails runner 'Rails.application.reloader.reload!'; then
echo
echo "---------------------------------------------"
echo
echo "::error::Zeitwerk reload failed - the app will not be able to reload properly in development."
echo "To reproduce locally, run \`bin/rails runner 'Rails.application.reloader.reload!'\`."
echo
exit 1
fi
- name: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --use-runtime-info --verbose --format=documentation --profile=50
- name: Plugin RSpec
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
run: bin/rake plugin:turbo_spec['*','--verbose --format=documentation --use-runtime-info --profile=50']
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake plugin:qunit['*']
timeout-minutes: 30
- name: Theme QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'themes'
run: DISCOURSE_DEV_DB=discourse_test bin/rake themes:qunit_all_official
timeout-minutes: 10
- uses: actions/upload-artifact@v4
if: always() && matrix.build_type == 'frontend' && matrix.target == 'plugins'
with:
name: ember-exam-execution-plugins-frontend-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
path: ./app/assets/javascripts/discourse/test-execution-*.json
- name: Ember Build for System Tests
if: matrix.build_type == 'system'
run: bin/ember-cli --build
- name: Minio cache
if: matrix.build_type == 'system' && matrix.target == 'core'
uses: actions/cache@v4
with:
path: ${{ env.MINIO_RUNNER_INSTALL_DIR }}
key: ${{ runner.os }}-${{ steps.container-envs.outputs.ruby_version }}-${{ steps.container-envs.outputs.debian_release }}-gem-${{ hashFiles('**/Gemfile.lock') }}
- name: Ensure latest minio binary installed for Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core'
run: bundle exec ruby script/install_minio_binaries.rb
- name: Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core'
env:
CHECKOUT_TIMEOUT: 10
run: RAILS_ENABLE_TEST_LOG=1 RAILS_TEST_LOG_LEVEL=error bin/turbo_rspec --use-runtime-info --profile=50 --verbose --format documentation spec/system
- name: Plugin System Tests
if: matrix.build_type == 'system' && matrix.target == 'plugins'
env:
CHECKOUT_TIMEOUT: 10
run: |
GLOBIGNORE="plugins/chat/*";
LOAD_PLUGINS=1 RAILS_ENABLE_TEST_LOG=1 RAILS_TEST_LOG_LEVEL=error bin/turbo_rspec --use-runtime-info --profile=50 --verbose --format documentation plugins/*/spec/system
shell: bash
timeout-minutes: 30
- name: Chat System Tests
if: matrix.build_type == 'system' && matrix.target == 'chat'
env:
CHECKOUT_TIMEOUT: 10
run: LOAD_PLUGINS=1 RAILS_ENABLE_TEST_LOG=1 RAILS_TEST_LOG_LEVEL=error bin/turbo_rspec --use-runtime-info --profile=50 --verbose --format documentation plugins/chat/spec/system
timeout-minutes: 30
- name: Theme System Tests
if: matrix.build_type == 'system' && matrix.target == 'themes'
env:
CHECKOUT_TIMEOUT: 10
run: |
RAILS_ENABLE_TEST_LOG=1 RAILS_TEST_LOG_LEVEL=error bin/turbo_rspec --profile=50 --verbose --format documentation tmp/themes/*/spec/system
shell: bash
timeout-minutes: 30
- name: Check for failed system test screenshots
id: check-failed-system-test-screenshots
if: always() && matrix.build_type == 'system'
run: |
if [ -d tmp/capybara ] && [ "$(ls -A tmp/capybara/)" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
shell: bash
- name: Upload failed system test screenshots
uses: actions/upload-artifact@v4
if: always() && steps.check-failed-system-test-screenshots.outputs.exists == 'true'
with:
name: failed-system-test-screenshots-${{ matrix.build_type }}-${{ matrix.target }}
path: tmp/capybara/*.png
- name: Check for flaky tests report
id: check-flaky-spec-report
if: always() && github.repository == 'discourse/discourse' && ${{ env.DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS == '1' }}
run: |
if [ -f tmp/turbo_rspec_flaky_tests.json ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
shell: bash
- name: Fetch Job ID
id: fetch-job-id
if: always() && steps.check-flaky-spec-report.outputs.exists == 'true'
run: |
job_id=$(ruby script/get_github_workflow_run_job_id.rb ${{ github.run_id }} ${{ github.run_attempt }} '${{ matrix.target }} ${{ matrix.build_type }}')
echo "job_id=$job_id" >> $GITHUB_OUTPUT
- name: Create flaky tests report artifact
if: always() && steps.check-flaky-spec-report.outputs.exists == 'true'
run: cp tmp/turbo_rspec_flaky_tests.json tmp/turbo_rspec_flaky_tests-${{ matrix.build_type }}-${{ matrix.target }}-${{ steps.fetch-job-id.outputs.job_id }}.json
- name: Upload flaky tests report
uses: actions/upload-artifact@v4
if: always() && steps.check-flaky-spec-report.outputs.exists == 'true'
with:
name: flaky-test-reports-${{ matrix.build_type }}-${{ matrix.target }}
path: tmp/turbo_rspec_flaky_tests-${{ matrix.build_type }}-${{ matrix.target }}-${{ steps.fetch-job-id.outputs.job_id }}.json
- name: Check Annotations
if: matrix.build_type == 'annotations'
run: |
bin/rake annotate:ensure_all_indexes
bin/annotate --models --model-dir app/models
if [ ! -z "$(git status --porcelain app/models/)" ]; then
echo "Core annotations are not up to date. To resolve, run:"
echo " bin/rake annotate:clean"
echo
echo "Or manually apply the diff printed below:"
echo "---------------------------------------------"
git -c color.ui=always diff app/models/
exit 1
fi
timeout-minutes: 30
core_frontend_tests:
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: core frontend (${{ matrix.browser }})
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-22.04-8core') || 'debian-12' }}
container:
image: discourse/discourse_test:slim-browsers
options: --user discourse
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
browser: ["Chrome", "Firefox ESR", "Firefox Evergreen"]
env:
TESTEM_BROWSER: ${{ (startsWith(matrix.browser, 'Firefox') && 'Firefox') || matrix.browser }}
TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
CHEAP_SOURCE_MAPS: "1"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: pnpm install
run: pnpm install --frozen-lockfile
- name: Ember Build
working-directory: ./app/assets/javascripts/discourse
run: |
mkdir /tmp/emberbuild
pnpm ember build --environment=test -o /tmp/emberbuild
- name: Core QUnit
working-directory: ./app/assets/javascripts/discourse
run: |
pnpm ember exam --path /tmp/emberbuild --load-balance --parallel=$(($(nproc) / 2)) --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
timeout-minutes: 15
- uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: ember-exam-execution-${{ matrix.browser }}-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
path: ./app/assets/javascripts/discourse/test-execution-*.json
merge:
if: github.repository == 'discourse/discourse' && github.ref == 'refs/heads/main'
runs-on: debian-12
needs: build
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: failed-system-test-screenshots
pattern: failed-system-test-screenshots-*
delete-merged: true
separate-directories: false
# Don't fail the job if there aren't any artifacts to merge.
continue-on-error: true
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: flaky-test-reports
pattern: flaky-test-reports-*
delete-merged: true
separate-directories: false
# Don't fail the job if there aren't any artifacts to merge.
continue-on-error: true