Reorganize test jobs by level

Signed-off-by: Rémy Coutable <remy@rymai.me>
This commit is contained in:
Rémy Coutable 2019-04-18 22:08:04 +02:00
parent 27d207bd8f
commit 4d9c76e9e4
No known key found for this signature in database
GPG Key ID: 98DFFD1C0C62B70B
15 changed files with 280 additions and 41 deletions

View File

@ -13,10 +13,8 @@ variables:
BUILD_ASSETS_IMAGE: "false" BUILD_ASSETS_IMAGE: "false"
before_script: before_script:
- bundle --version
- date - date
- source scripts/utils.sh - source scripts/utils.sh
- date
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
- date - date

View File

@ -6,8 +6,9 @@
.use-pg-10: &use-pg-10 .use-pg-10: &use-pg-10
services: services:
- postgres:10.7 - name: postgres:10.7
- redis:alpine command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
.use-mysql: &use-mysql .use-mysql: &use-mysql
services: services:
@ -52,8 +53,10 @@
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
- TEST_TOOL=${JOB_NAME[0]} - TEST_TOOL=${JOB_NAME[0]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - TEST_LEVEL=${JOB_NAME[1]}
- export KNAPSACK_GENERATE_REPORT=true - DATABASE=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_${TEST_LEVEL}_${DATABASE}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true KNAPSACK_LOG_LEVEL=debug KNAPSACK_TEST_DIR=spec
- export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH} - export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH}
- export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
@ -63,7 +66,10 @@
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}' - '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}' - '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- scripts/gitaly-test-spawn - scripts/gitaly-test-spawn
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml" - date
- 'export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./lib/quality/test_level.rb -e "puts Quality::TestLevel.new.pattern(:${TEST_LEVEL})")'
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml --tag level:${TEST_LEVEL} --tag ~geo"
- date
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always when: always
@ -140,19 +146,47 @@ setup-test-env:
except: except:
- /(^docs[\/-].*|.*-docs$)/ - /(^docs[\/-].*|.*-docs$)/
rspec-pg: rspec unit pg:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg
parallel: 50 parallel: 20
rspec-pg-10: rspec integration pg:
<<: *rspec-metadata-pg
parallel: 6
rspec system pg:
<<: *rspec-metadata-pg
parallel: 24
rspec unit pg-10:
<<: *rspec-metadata-pg-10 <<: *rspec-metadata-pg-10
<<: *only-schedules-master <<: *only-schedules-master
parallel: 50 parallel: 20
rspec-mysql: rspec integration pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 6
rspec system pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 24
rspec unit mysql:
<<: *rspec-metadata-mysql <<: *rspec-metadata-mysql
<<: *only-schedules-master <<: *only-schedules-master
parallel: 50 parallel: 20
rspec integration mysql:
<<: *rspec-metadata-mysql
<<: *only-schedules-master
parallel: 6
rspec system mysql:
<<: *rspec-metadata-mysql
<<: *only-schedules-master
parallel: 24
rspec-fast-spec-helper: rspec-fast-spec-helper:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg
@ -164,7 +198,7 @@ rspec-fast-spec-helper:
script: script:
- export CACHE_CLASSES=true - export CACHE_CLASSES=true
- scripts/gitaly-test-spawn - scripts/gitaly-test-spawn
- bin/rspec --color --format documentation --tag quarantine spec/ - bin/rspec --color --format documentation --tag quarantine -- spec/
rspec-pg-quarantine: rspec-pg-quarantine:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg

View File

@ -40,12 +40,12 @@ update-tests-metadata:
policy: push policy: push
script: script:
- retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document - retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_*_pg_node_*.json
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH} - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH' - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json - rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
- scripts/insert-rspec-profiling-data - scripts/insert-rspec-profiling-data
only: only:

View File

@ -108,11 +108,13 @@ To make sure that indices still fit. You could find great details in:
In order to run the test you can use the following commands: In order to run the test you can use the following commands:
- `rake spec` to run the rspec suite - `bin/rake spec` to run the rspec suite
- `rake karma` to run the karma test suite - `bin/rake spec:unit` to run the only the unit tests
- `rake gitlab:test` to run all the tests - `bin/rake spec:integration` to run the only the integration tests
- `bin/rake spec:system` to run the only the system tests
- `bin/rake karma` to run the karma test suite
Note: `rake spec` takes significant time to pass. Note: `bin/rake spec` takes significant time to pass.
Instead of running full test suite locally you can save a lot of time by running Instead of running full test suite locally you can save a lot of time by running
a single test or directory related to your changes. After you submit merge request a single test or directory related to your changes. After you submit merge request
CI will run full test suite for you. Green CI status in the merge request means CI will run full test suite for you. Green CI status in the merge request means
@ -121,6 +123,9 @@ full test suite is passed.
Note: You can't run `rspec .` since this will try to run all the `_spec.rb` Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
files it can find, also the ones in `/tmp` files it can find, also the ones in `/tmp`
Note: You can pass RSpec command line options to the `spec:unit`,
`spec:integration`, and `spec:system` tasks, e.g. `bin/rake "spec:unit[--tag ~geo --dry-run]"`.
To run a single test file you can use: To run a single test file you can use:
- `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test - `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test

View File

@ -4,12 +4,14 @@
_This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._ _This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._
As of 2019-04-16, we have the following distribution of tests per level: As of 2019-05-01, we have the following distribution of tests per level:
- 67 black-box tests at the system level (aka end-to-end or QA tests) in CE, 98 in EE. This represents 0.3% of all the CE tests (0.3% in EE). | Test level | Community Edition | Enterprise Edition | Community + Enterprise Edition |
- 5,457 white-box tests at the system level (aka system or feature tests) in CE, 6,585 in EE. This represents 24.6% of all the CE tests (20.3% in EE). | --------- | ---------- | -------------- | ----- |
- 8,298 integration tests in CE, 10,633 in EE: 0.3% of all the CE tests (0.3% in EE). This represents 37.2% of all the CE tests (32.8% in EE). | Black-box tests at the system level (aka end-to-end or QA tests) | 68 (0.14%) | 31 (0.2%) | 99 (0.17%) |
- 8,403 unit tests in CE, 15,090 in EE: 0.3% of all the CE tests (0.3% in EE). This represents 37.8% of all the CE tests (46.6% in EE). | White-box tests at the system level (aka system or feature tests) | 5,471 (11.9%) | 969 (7.4%) | 6440 (10.9%) |
| Integration tests | 8,333 (18.2%) | 2,244 (17.2%) | 10,577 (17.9%) |
| Unit tests | 32,031 (69.7%) | 9,778 (75.1%) | 41,809 (71%) |
## Unit tests ## Unit tests

75
lib/quality/test_level.rb Normal file
View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
module Quality
class TestLevel
UnknownTestLevelError = Class.new(StandardError)
TEST_LEVEL_FOLDERS = {
unit: %w[
bin
config
db
dependencies
factories
finders
frontend
graphql
helpers
initializers
javascripts
lib
migrations
models
policies
presenters
rack_servers
routing
rubocop
serializers
services
sidekiq
tasks
uploaders
validators
views
workers
elastic_integration
],
integration: %w[
controllers
mailers
requests
],
system: ['features']
}.freeze
attr_reader :prefix
def initialize(prefix = nil)
@prefix = prefix
@patterns = {}
@regexps = {}
end
def pattern(level)
@patterns[level] ||= "#{prefix}spec/{#{TEST_LEVEL_FOLDERS.fetch(level).join(',')}}{,/**/}*_spec.rb".freeze
end
def regexp(level)
@regexps[level] ||= Regexp.new("#{prefix}spec/(#{TEST_LEVEL_FOLDERS.fetch(level).join('|')})").freeze
end
def level_for(file_path)
case file_path
when regexp(:unit)
:unit
when regexp(:integration)
:integration
when regexp(:system)
:system
else
raise UnknownTestLevelError, "Test level for #{file_path} couldn't be set. Please rename the file properly or change the test level detection regexes in #{__FILE__}."
end
end
end
end

View File

@ -1,7 +1,32 @@
# frozen_string_literal: true
return if Rails.env.production?
Rake::Task["spec"].clear if Rake::Task.task_defined?('spec') Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
namespace :spec do namespace :spec do
desc 'GitLab | Rspec | Run request specs' desc 'GitLab | RSpec | Run unit tests'
RSpec::Core::RakeTask.new(:unit, :rspec_opts) do |t, args|
require_dependency 'quality/test_level'
t.pattern = Quality::TestLevel.new.pattern(:unit)
t.rspec_opts = args[:rspec_opts]
end
desc 'GitLab | RSpec | Run integration tests'
RSpec::Core::RakeTask.new(:integration, :rspec_opts) do |t, args|
require_dependency 'quality/test_level'
t.pattern = Quality::TestLevel.new.pattern(:integration)
t.rspec_opts = args[:rspec_opts]
end
desc 'GitLab | RSpec | Run system tests'
RSpec::Core::RakeTask.new(:system, :rspec_opts) do |t, args|
require_dependency 'quality/test_level'
t.pattern = Quality::TestLevel.new.pattern(:system)
t.rspec_opts = args[:rspec_opts]
end
desc '[Deprecated] Use the "bin/rspec --tag api" instead'
task :api do task :api do
cmds = [ cmds = [
%w(rake gitlab:setup), %w(rake gitlab:setup),
@ -10,7 +35,7 @@ namespace :spec do
run_commands(cmds) run_commands(cmds)
end end
desc 'GitLab | Rspec | Run feature specs' desc '[Deprecated] Use the "spec:system" task instead'
task :feature do task :feature do
cmds = [ cmds = [
%w(rake gitlab:setup), %w(rake gitlab:setup),
@ -19,7 +44,7 @@ namespace :spec do
run_commands(cmds) run_commands(cmds)
end end
desc 'GitLab | Rspec | Run model specs' desc '[Deprecated] Use "bin/rspec spec/models" instead'
task :models do task :models do
cmds = [ cmds = [
%w(rake gitlab:setup), %w(rake gitlab:setup),
@ -28,7 +53,7 @@ namespace :spec do
run_commands(cmds) run_commands(cmds)
end end
desc 'GitLab | Rspec | Run service specs' desc '[Deprecated] Use "bin/rspec spec/services" instead'
task :services do task :services do
cmds = [ cmds = [
%w(rake gitlab:setup), %w(rake gitlab:setup),
@ -37,7 +62,7 @@ namespace :spec do
run_commands(cmds) run_commands(cmds)
end end
desc 'GitLab | Rspec | Run lib specs' desc '[Deprecated] Use "bin/rspec spec/lib" instead'
task :lib do task :lib do
cmds = [ cmds = [
%w(rake gitlab:setup), %w(rake gitlab:setup),
@ -45,15 +70,6 @@ namespace :spec do
] ]
run_commands(cmds) run_commands(cmds)
end end
desc 'GitLab | Rspec | Run other specs'
task :other do
cmds = [
%w(rake gitlab:setup),
%w(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services)
]
run_commands(cmds)
end
end end
desc "GitLab | Run specs" desc "GitLab | Run specs"

View File

@ -5,6 +5,7 @@ export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet" export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet"
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
bundle --version
bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
fi fi

View File

@ -0,0 +1,105 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Quality::TestLevel do
describe '#pattern' do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
.to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
end
end
context 'when level is integration' do
it 'returns a pattern' do
expect(subject.pattern(:integration))
.to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb")
end
end
context 'when level is system' do
it 'returns a pattern' do
expect(subject.pattern(:system))
.to eq("spec/{features}{,/**/}*_spec.rb")
end
end
context 'with a prefix' do
it 'returns a pattern' do
expect(described_class.new('ee/').pattern(:system))
.to eq("ee/spec/{features}{,/**/}*_spec.rb")
end
end
describe 'performance' do
it 'memoizes the pattern for a given level' do
expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id)
end
it 'freezes the pattern for a given level' do
expect(subject.pattern(:system)).to be_frozen
end
end
end
describe '#regexp' do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
.to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
end
end
context 'when level is integration' do
it 'returns a regexp' do
expect(subject.regexp(:integration))
.to eq(%r{spec/(controllers|mailers|requests)})
end
end
context 'when level is system' do
it 'returns a regexp' do
expect(subject.regexp(:system))
.to eq(%r{spec/(features)})
end
end
context 'with a prefix' do
it 'returns a regexp' do
expect(described_class.new('ee/').regexp(:system))
.to eq(%r{ee/spec/(features)})
end
end
describe 'performance' do
it 'memoizes the regexp for a given level' do
expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id)
end
it 'freezes the regexp for a given level' do
expect(subject.regexp(:system)).to be_frozen
end
end
end
describe '#level_for' do
it 'returns the correct level for a unit test' do
expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit)
end
it 'returns the correct level for an integration test' do
expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration)
end
it 'returns the correct level for a system test' do
expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system)
end
it 'raises an error for an unknown level' do
expect { subject.level_for('spec/unknown/foo_spec.rb') }
.to raise_error(described_class::UnknownTestLevelError,
%r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.})
end
end
end

View File

@ -44,6 +44,8 @@ Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
quality_level = Quality::TestLevel.new
RSpec.configure do |config| RSpec.configure do |config|
config.use_transactional_fixtures = false config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false config.use_instantiated_fixtures = false
@ -55,9 +57,10 @@ RSpec.configure do |config|
config.infer_spec_type_from_file_location! config.infer_spec_type_from_file_location!
config.full_backtrace = !!ENV['CI'] config.full_backtrace = !!ENV['CI']
config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
location = metadata[:location] location = metadata[:location]
metadata[:level] = quality_level.level_for(location)
metadata[:api] = true if location =~ %r{/spec/requests/api/} metadata[:api] = true if location =~ %r{/spec/requests/api/}
# do not overwrite type if it's already set # do not overwrite type if it's already set