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: ${{ (matrix.build_type == 'annotations') && 'ubuntu-latest' || 'ubuntu-22.04-8core' }} container: discourse/discourse_test:slim${{ (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') && github.ref == 'refs/main/head' }} CHEAP_SOURCE_MAPS: "1" TESTEM_DEFAULT_BROWSER: Chrome 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: Remove Chromium continue-on-error: true if: matrix.build_type == 'system' run: apt remove -y chromium-driver chromium - 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: | echo "QUNIT_PARALLEL=$(($(nproc) / 2))" >> $GITHUB_ENV - 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 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: 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: Get yarn cache directory id: yarn-cache-dir run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Yarn cache uses: actions/cache@v4 id: yarn-cache with: path: ${{ steps.yarn-cache-dir.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-cachev2 - name: Yarn install run: yarn install --frozen-lockfile - name: Checkout official plugins if: matrix.target == 'plugins' run: bin/rake plugin:install_all_official # Remove discourse-ai from stable as it is not compatible with Ruby 3.3 which we are now using for the base image - name: Remove discourse-ai from stable if: matrix.target == 'plugins' && (github.ref_name == 'stable' || github.base_ref == 'stable') run: rm -rf plugins/discourse-ai - name: Pull compatible versions of plugins if: matrix.target == 'plugins' 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: Fetch app state cache uses: actions/cache@v4 id: app-cache with: path: tmp/app-cache key: >- ${{ runner.name }}- ${{ hashFiles('.github/workflows/tests.yml') }}- ${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}- ${{ hashFiles('config/environments/test.rb') }}- ${{ env.USES_PARALLEL_DATABASES }} - 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 - name: Plugin RSpec if: matrix.build_type == 'backend' && matrix.target == 'plugins' run: bin/rake plugin:turbo_spec['*','--verbose --format documentation --use-runtime-info'] - name: Plugin QUnit if: matrix.build_type == 'frontend' && matrix.target == 'plugins' run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake plugin:qunit['*','1200000'] 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: 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@v3 # TODO (tgxworld): V4 doesn't allow us to upload multiple uploads to the same artifact name so keep at V3 for now. if: always() && steps.check-failed-system-test-screenshots.outputs.exists == 'true' with: name: failed-system-test-screenshots path: tmp/capybara/*.png - name: Check for flaky tests report id: check-flaky-spec-report if: always() && github.repository == 'discourse/discourse' 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@v3 # TODO (tgxworld): V4 doesn't allow us to upload multiple uploads to the same artifact name so keep at V3 for now if: always() && steps.check-flaky-spec-report.outputs.exists == 'true' with: name: flaky-test-reports 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: ubuntu-22.04-8core 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: Get yarn cache directory id: yarn-cache-dir run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Yarn cache uses: actions/cache@v4 id: yarn-cache with: path: ${{ steps.yarn-cache-dir.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-cachev2 - name: Yarn install run: yarn install --frozen-lockfile - name: Ember Build working-directory: ./app/assets/javascripts/discourse run: | mkdir /tmp/emberbuild yarn ember build --environment=test -o /tmp/emberbuild - name: Core QUnit working-directory: ./app/assets/javascripts/discourse run: | yarn ember exam --path /tmp/emberbuild --load-balance --parallel=$(($(nproc) / 2 + 1)) --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