Maintenance: Speed up the CI pipeline and report test results to GitLab.

Approved-by: Martin Gruner <mg@zammad.com>
This commit is contained in:
Florian Liebe
2026-09-30 12:03:58 +02:00
parent 0e13236e75
commit 17e0e558eb
31 changed files with 219 additions and 84 deletions

View File

@@ -4,9 +4,6 @@ default:
tags:
- docker
# Most jobs execute ruby commands, so take that as default.
cache: !reference [.cache, read_only_ruby]
# Artifacts are stored for failed jobs
artifacts:
expire_in: 1 week
@@ -18,16 +15,20 @@ default:
interruptible: true
# Initialize application env
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
after_script:
- .gitlab/environment_info.sh > log/environment_info.log
# Copy logs from other containers to store them as artifacts.
- cp /builds/*.log log/ || true
# Print recorded test runtimes for script/build/update_test_timings.rb. The script is
# missing if an older commit is checked out, e.g. in rspec:migration.
- if [ -x .gitlab/print_test_timings.sh ]; then .gitlab/print_test_timings.sh; fi
# Logs and screenshots are only needed for failed jobs, see .artifacts_with_test_report.
- if [ "$CI_JOB_STATUS" = success ]; then rm -rf log/*.log tmp/screenshot* tmp/playwright-traces; fi
# For jobs running the application's Ruby code: read the gem cache and initialize the
# application environment.
.template_ruby:
cache: !reference [.cache, read_only_ruby]
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]

View File

@@ -0,0 +1,48 @@
# Building blocks for `needs:`, combined per job:
#
# needs:
# - !reference [.needs, ruby]
# - !reference [.needs, lint]
#
# A job with `needs:` starts as soon as the listed jobs have finished, instead of waiting
# for all jobs of the earlier stages, and downloads the artifacts of the listed jobs only.
#
# Jobs of the test and browser stages need `lint`, so that none of them is started for a
# commit that fails linting. The manual lint jobs are not listed, a job needing them
# would wait for them forever.
#
# Jobs with rules of their own are optional, because filtered pipelines ($JOB_FILTER,
# $BROWSER_TEST_FILTER) do not contain them, and needing a job that is missing from the
# pipeline fails its creation. Without 'write database dumps', .gitlab/init_database.sh
# initializes the database from scratch.
.needs:
ruby:
- job: 'write cache: ruby'
artifacts: false
nodejs:
- job: 'write cache: nodejs'
artifacts: false
database:
- job: 'write database dumps'
optional: true
assets:
- job: 'assets:build'
lint:
- job: 'lint: shellcheck'
artifacts: false
optional: true
- job: 'lint: i18n & rails'
artifacts: false
optional: true
- job: 'lint: ruby & security'
artifacts: false
optional: true
- job: 'lint: markdown, coffee & css'
artifacts: false
optional: true
- job: 'lint: js'
artifacts: false
optional: true
- job: 'lint: secret_detection'
artifacts: false
optional: true

View File

@@ -0,0 +1,16 @@
# For the jobs writing JUnit reports (see CI_JUNIT_REPORT_DIR): upload the artifacts of
# passed jobs, too, so that the test reports of pipelines and merge requests contain
# all tests and not only the failed ones. The default after_script removes the logs and
# screenshots of passed jobs, so that only the report of these is uploaded.
.artifacts_with_test_report:
artifacts:
expire_in: 1 week
when: always
paths:
- tmp/screenshot*
- tmp/screenshots/*
# Recorded by spec/support/capybara/playwright_traces.rb in the Playwright lanes.
- tmp/playwright-traces/*
- log/*.log
reports:
junit: tmp/junit/*.xml

View File

@@ -29,6 +29,10 @@ variables:
# Record the runtime of each test file, used to keep .gitlab/ci/timings/ up to date.
CI_TEST_TIMINGS_PATH: 'tmp/test_timings/timings.yml'
# Write JUnit reports for the test reports of GitLab, see spec/support/junit_report.rb and
# .artifacts_with_test_report.
CI_JUNIT_REPORT_DIR: 'tmp/junit'
# Branches that are only ever built through their merge request: feature branches of
# developers (private/*) and branches created by automation (cherry-picks, reverts,
# dependency updates). Used in rules with `=~`, which requires the pattern to be

View File

@@ -11,4 +11,10 @@ workflow:
# Don't create branch pipelines for pushes to private branches (as there will also be a MR for them).
- if: $CI_COMMIT_BRANCH =~ $CI_ZAMMAD_MR_ONLY_BRANCH_PATTERN && $CI_PIPELINE_SOURCE == "push"
when: never
# A dependency update that breaks one job almost always breaks many others, so the
# first failure is enough and the remaining jobs are canceled.
- if: $CI_COMMIT_REF_NAME =~ /^renovate\//
when: always
auto_cancel:
on_job_failure: all
- when: always

View File

@@ -3,14 +3,12 @@
image:
name: alpine/curl:latest
entrypoint: ['']
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /weblate-merge$/ && $CI_MERGE_REQUEST_SQUASH_ON_MERGE == "true"'
when: on_success
- when: never
before_script: []
script:
- echo "⚠️ Disabling squash option for this MR..."
- |
@@ -81,7 +79,6 @@
# GITLAB_API_TOKEN - read_api token for the MR query (defaults to RENOVATE_TOKEN)
'dependency digest':
stage: automate
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters_without_force]
@@ -108,14 +105,12 @@
image:
name: ghcr.io/updatecli/updatecli:latest
entrypoint: ['']
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters_without_force]
- if: ($CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web") && $CI_COMMIT_REF_NAME == "develop"
when: on_success
- when: never
before_script: []
script:
- updatecli pipeline apply --config .dev/updatecli.yaml --values .dev/updatecli-values-enable-gitlab.yaml
after_script: []

View File

@@ -1,8 +1,16 @@
.template_browser-core_capybara:
stage: browser
extends:
- .template_ruby
- .job_rules_default
- .tags_docker_low_concurrency
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, assets]
- !reference [.needs, lint]
timeout: 40 minutes
script:
- !reference [.scripts, zammad_ci_test_prepare]
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast -t ~integration -t ~searchindex spec/system
@@ -37,13 +45,3 @@
- !reference [.services, postgresql]
- !reference [.services, playwright]
- !reference [.services, redis]
# A job-level artifacts block replaces the global default one entirely -
# repeat its paths and add the traces recorded by playwright_traces.rb.
artifacts:
expire_in: 1 week
when: on_failure
paths:
- tmp/screenshot*
- tmp/screenshots/*
- tmp/playwright-traces/*
- log/*.log

View File

@@ -1,6 +1,7 @@
.template_browser-core_capybara_es:
extends:
- .template_browser-core_capybara
timeout: 30 minutes
script:
- !reference [.scripts, zammad_ci_test_prepare]
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast -t ~integration -t searchindex spec/system
@@ -38,16 +39,6 @@
- !reference [.services, elasticsearch]
- !reference [.services, playwright]
- !reference [.services, redis]
# A job-level artifacts block replaces the global default one entirely -
# repeat its paths and add the traces recorded by playwright_traces.rb.
artifacts:
expire_in: 1 week
when: on_failure
paths:
- tmp/screenshot*
- tmp/screenshots/*
- tmp/playwright-traces/*
- log/*.log
capybara:es:chrome:
extends:

View File

@@ -4,6 +4,8 @@
# replaced, not merged).
rules:
- !reference [.rule_fragments, then_browser_test_filter_only]
# Looping a single spec takes much longer than a regular slice of the suite.
timeout: 2 hours
variables:
BROWSER_TEST_FILTER_LOOP_COUNT: 50
script:

View File

@@ -3,6 +3,7 @@ capybara:integration:other:
- .template_browser-core_capybara_chrome
- .variables_idoit
- .variables_proxy
timeout: 20 minutes
# These tests need third party service containers and are slow, so they are
# advisory in merge requests.
rules:
@@ -23,6 +24,7 @@ capybara:integration:saml:
extends:
- .template_browser-core_capybara_chrome
- .variables_auth
timeout: 20 minutes
# These tests need third party service containers and are slow, so they are
# advisory in merge requests.
rules:
@@ -40,6 +42,7 @@ capybara:integration:saml:
.template_capybara_integration_otrs:
extends:
- .template_browser-core_capybara_chrome
timeout: 20 minutes
# These tests need third party service containers and are slow, so they are
# advisory in merge requests.
rules:

View File

@@ -5,8 +5,10 @@
'write database dumps':
stage: lint
extends:
- .template_ruby
- .job_rules_default
timeout: 6 minutes
needs: !reference [.needs, ruby]
services:
- !reference [.services, postgresql]
variables:

View File

@@ -3,16 +3,18 @@
stage: lint
extends:
- .job_rules_default
# Start right away: the caches written in the pre stage only change with the lock
# files, so they are usually filled already.
needs: []
.template_lint_rails:
extends:
- .template_lint
- .template_ruby
services:
- !reference [.services, postgresql]
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
- !reference [.template_ruby, before_script]
- !reference [.scripts, zammad_db_init]
variables:
ZAMMAD_SAFE_MODE: 1
@@ -23,10 +25,8 @@
- .template_lint
timeout: 2 minutes
image: koalaman/shellcheck-alpine:stable
cache: []
before_script: []
script:
- shellcheck -S warning $(find . -name "*.sh" -o -name "functions" | egrep -v "/vendor|node_modules/")
- find . \( -name vendor -o -name node_modules \) -prune -o \( -name '*.sh' -o -name functions \) -print0 | xargs -0 shellcheck -S warning
after_script: []
'lint: i18n & rails':
@@ -49,6 +49,7 @@
'lint: ruby & security':
extends:
- .template_lint
- .template_ruby
timeout: 10 minutes
before_script:
- !reference [.scripts, source_rvm]
@@ -146,12 +147,10 @@
image:
name: 'zricethezav/gitleaks:latest'
entrypoint: ['']
cache: []
timeout: 2 minutes
variables:
# Go uses all CPUs by default.
GOMAXPROCS: 1
before_script: []
script:
# Deep enough to contain the pipeline's base commit, so only the new commits are scanned.
# Setting GIT_DEPTH for this job has no effect, the checkout keeps the depth of 1.

View File

@@ -14,14 +14,12 @@
'write cache: nodejs':
stage: pre
cache: !reference [.cache, read_write_nodejs]
before_script: []
script:
- !reference [.scripts, pnpm_install_all]
after_script: []
'push to github':
stage: pre
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.
@@ -43,7 +41,6 @@
stage: pre
tags:
- push-to-ftp
cache: []
rules:
- !reference [.rule_fragments, main_repository_only]
# Don't apply job filter here.

View File

@@ -56,7 +56,7 @@
# Build/update docker image and publish it to the GitLab container registry.
- |
echo "Build docker image…"
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
echo -e "\\e[0Ksection_start:`date +%s`:docker_build[collapsed=true]\\r\\e[0Kdocker build"
if [ ! "${SKIP_DOCKER_BUILD}" ]
then
@@ -231,7 +231,6 @@
tags:
- review-apps
stage: pre
cache: []
variables:
SERVER_NAME: review-apps.dc.zammad.com
APP_SLUG: $CI_COMMIT_REF_SLUG
@@ -242,6 +241,8 @@
environment:
name: ${APP_NAME}
url: https://${APP_FQDN}#getting_started/auto_wizard
# Deploying, stopping and restarting the same review app must not overlap.
resource_group: ${APP_NAME}
rules:
- !reference [.rule_fragments, main_repository_only]
- if: $CI_MERGE_REQUEST_ID

View File

@@ -1,8 +1,13 @@
.template_api_client:
stage: test
extends:
- .template_ruby
- .job_rules_default
- .tags_docker_low_concurrency
needs:
- !reference [.needs, ruby]
- !reference [.needs, lint]
timeout: 15 minutes
services:
- !reference [.services, postgresql]
- !reference [.services, redis]

View File

@@ -1,9 +1,15 @@
assets:build:
stage: test
needs:
- !reference [.needs, ruby]
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
# No `rules:` on purpose: the browser jobs of a filtered pipeline ($JOB_FILTER,
# $BROWSER_TEST_FILTER) still need the assets built here, as the specs
# themselves skip the compilation ($CI_SKIP_ASSETS_PRECOMPILE).
extends:
- .template_ruby
- .no_artifact_dependencies
services:
- !reference [.services, postgresql]
@@ -33,8 +39,14 @@ assets:build:
assets:build:pwa:
stage: test
extends:
- .template_ruby
- .job_rules_default
- .no_artifact_dependencies
needs:
- !reference [.needs, ruby]
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
services:
- !reference [.services, postgresql]
cache: !reference [.cache, read_only_full]

View File

@@ -6,11 +6,12 @@
# The job only needs the repository checkout. Without this, it implicitly
# depends on the artifacts of all earlier stages, so retrying it after those
# artifacts expired fails with `missing_dependency_failure`.
needs: []
needs:
- !reference [.needs, lint]
timeout: 15 minutes
variables:
DOCKER_HOST: unix:///var/run/docker.sock
DEVCONTAINERS_CLI_VERSION: '0.89.0'
cache: []
before_script:
- apk add --no-cache docker-cli docker-cli-buildx docker-compose
after_script: []

View File

@@ -3,6 +3,9 @@
stage: test
extends:
- .no_artifact_dependencies
needs:
- !reference [.needs, lint]
timeout: 30 minutes
interruptible: false
rules:
- !reference [.rule_fragments, main_repository_only]
@@ -12,13 +15,12 @@
allow_failure: true
- when: on_success
services: []
cache: []
variables:
ZAMMAD_IMAGE_TAG: "ci-${CI_JOB_ID}-${CI_COMMIT_SHORT_SHA}"
ZAMMAD_IMAGE_REPO: "zammad"
ZAMMAD_IMAGE: "${ZAMMAD_IMAGE_REPO}:${ZAMMAD_IMAGE_TAG}"
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
- git clone --depth=1 https://github.com/zammad/zammad-docker-compose.git
# Make the stack use the image that was just built.
- echo "IMAGE_REPO=${ZAMMAD_IMAGE_REPO}" > zammad-docker-compose/.env

View File

@@ -3,13 +3,19 @@
extends:
- .job_rules_default
- .no_artifact_dependencies
- .artifacts_with_test_report
needs:
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
cache: !reference [.cache, read_only_nodejs]
before_script:
- !reference [.scripts, pnpm_install]
- !reference [.scripts, pnpm_install_eslint_plugin_zammad]
script:
# Only run one worker to avoid load peaks affecting other jobs.
- pnpm test --max-workers=1 --shard $CI_NODE_INDEX/$CI_NODE_TOTAL
# Vitest resolves a relative output file against app/frontend, its root.
- pnpm test --max-workers=1 --shard $CI_NODE_INDEX/$CI_NODE_TOTAL --reporter=default --reporter=junit --outputFile.junit=$CI_PROJECT_DIR/$CI_JUNIT_REPORT_DIR/vitest.xml
# Stop here, except on the first shard.
- "[[ $CI_NODE_INDEX = 1 ]] || exit 0"
- echo "Also test eslint-plugin-zammad…"
@@ -27,6 +33,10 @@ frontend:cypress:
- .job_rules_default
- .tags_docker_low_concurrency
- .no_artifact_dependencies
needs:
- !reference [.needs, nodejs]
- !reference [.needs, lint]
timeout: 15 minutes
cache: !reference [.cache, read_only_nodejs]
before_script:
- !reference [.scripts, pnpm_install_cypress]

View File

@@ -2,6 +2,9 @@ include:
- '.gitlab/ci/test/integration/*.yml'
.template_integration:
extends:
- .template_ruby
- .artifacts_with_test_report
# These tests partly talk to live third party services. On private branches their
# signal is advisory only, so a failure does not block the pipeline.
rules:
@@ -16,3 +19,8 @@ include:
- !reference [.services, redis]
- !reference [.services, memcached]
stage: test
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 15 minutes

View File

@@ -1,10 +1,18 @@
.template_integration_es:
extends:
- .template_ruby
- .artifacts_with_test_report
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
- !reference [.services, memcached]
- !reference [.services, elasticsearch]
stage: test
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 30 minutes
script:
- !reference [.scripts, zammad_db_init]
- bundle exec rspec --tag searchindex --tag ~type:system

View File

@@ -5,26 +5,31 @@ rspec:migration:
stage: test
extends:
- .job_rules_default
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, lint]
timeout: 20 minutes
cache: !reference [.cache, read_only_ruby]
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
# The database must be initialized by the code of the stable branch, so the database dump
# of this pipeline must not be used here.
dependencies: []
before_script: []
script:
# First, checkout stable and set it up.
- git fetch --unshallow
- git checkout stable-6.0
- git fetch --depth=1 origin stable-6.0
- git checkout FETCH_HEAD
- !reference [.scripts, source_rvm]
- rvm use 3.1.3
- rvm use $(cat .ruby-version)
- bundle config set without mysql
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
- !reference [.scripts, zammad_db_init]
# Then, switch to the current commit, migrate to it and run a few selected tests.
- git checkout $CI_COMMIT_SHA
- rvm use 3.4.9
- rvm use $(cat .ruby-version)
- !reference [.scripts, bundle_install]
# Force redis usage, even if it was disabled by the initial configure_environment script of stable.
- export REDIS_URL=redis://redis

View File

@@ -3,6 +3,9 @@
stage: test
extends:
- .no_artifact_dependencies
needs:
- !reference [.needs, lint]
timeout: 30 minutes
interruptible: false
allow_failure: true
retry: 2
@@ -16,8 +19,6 @@
when: on_success
- when: never
services: []
cache: []
before_script: []
script:
- .gitlab/packager.io/run_scenarios.sh
after_script:

View File

@@ -1,5 +1,8 @@
performance:
stage: test
needs:
- !reference [.needs, lint]
timeout: 45 minutes
rules:
- !reference [.rule_fragments, variable_filters]
- if: $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"
@@ -18,16 +21,15 @@ performance:
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
cache: []
before_script:
# Copy performance checking code to tmp/ so that it survives branch switching
- cp -rv .gitlab/performance tmp/
# First, checkout stable and set it up.
- git fetch --unshallow >/dev/null 2>&1
- git checkout stable-6.0
- git fetch --depth=1 origin stable-6.0
- git checkout FETCH_HEAD
- !reference [.scripts, source_rvm]
- rvm use 3.1.3
- rvm use $(cat .ruby-version)
- bundle config set without mysql
- !reference [.scripts, bundle_install]
- !reference [.scripts, configure_environment]
@@ -39,7 +41,7 @@ performance:
- bundle exec rails r tmp/performance/run_tests.rb || true
# Then, switch to the current commit, migrate to it and run a few selected tests.
- git checkout $CI_COMMIT_SHA
- rvm use 3.4.9
- rvm use $(cat .ruby-version)
- !reference [.scripts, bundle_install]
# Force redis usage, even if it was disabled by the initial configure_environment script of stable.
- export REDIS_URL=redis://redis

View File

@@ -4,7 +4,14 @@ include:
.template_rspec:
stage: test
extends:
- .template_ruby
- .job_rules_default
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 40 minutes
script:
- !reference [.scripts, zammad_db_init]
- bundle exec rspec --require ./spec/rspec_extensions.rb --exclude-pattern "spec/system/**/*_spec.rb" -t ~searchindex -t ~integration
@@ -19,9 +26,16 @@ include:
rspec:integration:
stage: test
extends:
- .template_ruby
- .variables_proxy
- .variables_mattermost
- .variables_s3
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 30 minutes
allow_failure: true
# Squid permits CONNECT to 443, 8443 and 3001 only (configs/squid-ci.conf in
# docker/zammad-proxies), so the SSL server of spec/lib/user_agent_spec.rb

View File

@@ -1,6 +1,14 @@
# Run back-end subscription add-on tests to check if it is still compatible.
addon:subscription:
stage: test
extends:
- .template_ruby
- .artifacts_with_test_report
needs:
- !reference [.needs, ruby]
- !reference [.needs, database]
- !reference [.needs, lint]
timeout: 15 minutes
rules:
- !reference [.rule_fragments, main_repository_only]
- !reference [.rule_fragments, variable_filters]

View File

@@ -1,7 +1,12 @@
.template_supabase:
stage: test
extends:
- .template_ruby
- .no_artifact_dependencies
needs:
- !reference [.needs, ruby]
- !reference [.needs, lint]
timeout: 10 minutes
tags:
- push-to-ftp # use only allow-listed ci runners
services:

View File

@@ -1,19 +0,0 @@
#!/bin/bash
date -u --rfc-3339=seconds
echo
top -b -n1 -w240
echo
ps aux --width 240
echo
free -m
echo
mount
echo
df -h
echo
echo "Last 100 lines from kernel log"
dmesg | tail -n 100
echo
echo "Last 1000 lines from system log (journalctl)"
journalctl -n 1000

View File

@@ -217,6 +217,7 @@ gem 'elasticsearch', '8.11.2', require: false
group :development, :test do
# test frameworks
gem 'rspec_junit_formatter'
gem 'rspec-rails'
gem 'rspec-retry'
gem 'shoulda-matchers'

View File

@@ -702,6 +702,8 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.13.7)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rszr (1.5.0)
rubocop (1.91.0)
json (>= 2.3)
@@ -1021,6 +1023,7 @@ DEPENDENCIES
rotp
rspec-rails
rspec-retry
rspec_junit_formatter
rszr
rubocop
rubocop-capybara

View File

@@ -0,0 +1,6 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
# Lets GitLab list all examples in the test reports of pipelines and merge requests.
if ENV['CI_JUNIT_REPORT_DIR'].present?
RSpec.configure { |config| config.add_formatter('RspecJunitFormatter', File.join(ENV['CI_JUNIT_REPORT_DIR'], 'rspec.xml')) }
end