Commit 53c990d0 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'caalberts-use-crystalball-with-knapsack' into 'master'

Use test detection based on crystalball mapping in rspec jobs (take 3)

See merge request gitlab-org/gitlab!49446
parents ae1f26ee 27efe215
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
RUBY_GC_MALLOC_LIMIT_MAX: 134217728 RUBY_GC_MALLOC_LIMIT_MAX: 134217728
CRYSTALBALL: "true" CRYSTALBALL: "true"
RECORD_DEPRECATIONS: "true" RECORD_DEPRECATIONS: "true"
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets", "detect-tests"]
script: script:
- *base-script - *base-script
- rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration" - rspec_paralellized_job "--tag ~quarantine --tag ~geo --tag ~level:migration"
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
- .rspec-base - .rspec-base
- .as-if-foss - .as-if-foss
- .use-pg11 - .use-pg11
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"] needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss", "detect-tests"]
.rspec-ee-base-pg11: .rspec-ee-base-pg11:
extends: extends:
......
...@@ -604,10 +604,9 @@ ...@@ -604,10 +604,9 @@
rules: rules:
- <<: *if-not-ee - <<: *if-not-ee
when: never when: never
- <<: *if-security-merge-request - <<: *if-default-refs
changes: *code-backstage-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:rspec-foss-impact: .rails:rules:rspec-foss-impact:
rules: rules:
......
...@@ -61,15 +61,17 @@ verify-tests-yml: ...@@ -61,15 +61,17 @@ verify-tests-yml:
- scripts/verify-tff-mapping - scripts/verify-tff-mapping
.detect-test-base: .detect-test-base:
image: ruby:2.7-alpine image: ruby:2.7
needs: [] needs: []
stage: prepare stage: prepare
script: script:
- source scripts/utils.sh - source ./scripts/utils.sh
- source ./scripts/rspec_helpers.sh
- install_gitlab_gem - install_gitlab_gem
- install_tff_gem - install_tff_gem
- tooling/bin/find_foss_tests ${MATCHED_TESTS_FILE} - retrieve_tests_mapping
- 'echo "test files affected: $(cat $MATCHED_TESTS_FILE)"' - 'if [ -n "$CI_MERGE_REQUEST_IID" ]; then tooling/bin/find_tests ${MATCHED_TESTS_FILE}; fi'
- 'if [ -n "$CI_MERGE_REQUEST_IID" ]; then echo "test files affected: $(cat $MATCHED_TESTS_FILE)"; fi'
artifacts: artifacts:
expire_in: 7d expire_in: 7d
paths: paths:
......
...@@ -20,6 +20,7 @@ class JobFinder ...@@ -20,6 +20,7 @@ class JobFinder
@job_query = options.delete(:job_query) @job_query = options.delete(:job_query)
@pipeline_id = options.delete(:pipeline_id) @pipeline_id = options.delete(:pipeline_id)
@job_name = options.delete(:job_name) @job_name = options.delete(:job_name)
@artifact_path = options.delete(:artifact_path)
# Force the token to be a string so that if api_token is nil, it's set to '', allowing unauthenticated requests (for forks). # Force the token to be a string so that if api_token is nil, it's set to '', allowing unauthenticated requests (for forks).
api_token = options.delete(:api_token).to_s api_token = options.delete(:api_token).to_s
...@@ -33,19 +34,31 @@ class JobFinder ...@@ -33,19 +34,31 @@ class JobFinder
end end
def execute def execute
find_job_with_filtered_pipelines || find_job_in_pipeline find_job_with_artifact || find_job_with_filtered_pipelines || find_job_in_pipeline
end end
private private
attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name, :artifact_path
def find_job_with_artifact
return if artifact_path.nil?
Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
return job if found_job_with_artifact?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
raise 'Job not found!'
end
def find_job_with_filtered_pipelines def find_job_with_filtered_pipelines
return if pipeline_query.empty? return if pipeline_query.empty?
Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline| Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job| Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
return job if job.name == job_name # rubocop:disable Cop/AvoidReturnFromBlocks return job if found_job_by_name?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end end
end end
...@@ -56,12 +69,22 @@ class JobFinder ...@@ -56,12 +69,22 @@ class JobFinder
return unless pipeline_id return unless pipeline_id
Gitlab.pipeline_jobs(project, pipeline_id, job_query_params).auto_paginate do |job| Gitlab.pipeline_jobs(project, pipeline_id, job_query_params).auto_paginate do |job|
return job if job.name == job_name # rubocop:disable Cop/AvoidReturnFromBlocks return job if found_job_by_name?(job) # rubocop:disable Cop/AvoidReturnFromBlocks
end end
raise 'Job not found!' raise 'Job not found!'
end end
def found_job_with_artifact?(job)
artifact_url = "https://gitlab.com/api/v4/projects/#{CGI.escape(project)}/jobs/#{job.id}/artifacts/#{artifact_path}"
response = HTTParty.head(artifact_url) # rubocop:disable Gitlab/HTTParty
response.success?
end
def found_job_by_name?(job)
job.name == job_name
end
def pipeline_query_params def pipeline_query_params
@pipeline_query_params ||= { per_page: 100, **pipeline_query } @pipeline_query_params ||= { per_page: 100, **pipeline_query }
end end
...@@ -95,6 +118,10 @@ if $0 == __FILE__ ...@@ -95,6 +118,10 @@ if $0 == __FILE__
options[:job_name] = value options[:job_name] = value
end end
opts.on("-a", "--artifact-path ARTIFACT_PATH", String, "A valid artifact path") do |value|
options[:artifact_path] = value
end
opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value| opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
options[:api_token] = value options[:api_token] = value
end end
......
#!/usr/bin/env bash #!/usr/bin/env bash
function retrieve_tests_metadata() { function retrieve_tests_metadata() {
mkdir -p crystalball/ knapsack/ rspec_flaky/ rspec_profiling/ mkdir -p knapsack/ rspec_flaky/ rspec_profiling/
local project_path="gitlab-org/gitlab" local project_path="gitlab-org/gitlab"
local test_metadata_job_id local test_metadata_job_id
...@@ -16,13 +16,6 @@ function retrieve_tests_metadata() { ...@@ -16,13 +16,6 @@ function retrieve_tests_metadata() {
if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then
scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}" scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
fi fi
# FIXME: We will need to find a pipeline where the $RSPEC_PACKED_TESTS_MAPPING_PATH.gz actually exists (Crystalball only runs every two-hours, but the `update-tests-metadata` runs for all `master` pipelines...).
# if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then
# (scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
# fi
#
# scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}"
} }
function update_tests_metadata() { function update_tests_metadata() {
...@@ -43,6 +36,21 @@ function update_tests_metadata() { ...@@ -43,6 +36,21 @@ function update_tests_metadata() {
fi fi
} }
function retrieve_tests_mapping() {
mkdir -p crystalball/
local project_path="gitlab-org/gitlab"
local test_metadata_with_mapping_job_id
test_metadata_with_mapping_job_id=$(scripts/api/get_job_id --project "${project_path}" -q "status=success" -q "ref=master" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz")
if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then
(scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_with_mapping_job_id}" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
fi
scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}"
}
function update_tests_mapping() { function update_tests_mapping() {
if ! crystalball_rspec_data_exists; then if ! crystalball_rspec_data_exists; then
echo "No crystalball rspec data found." echo "No crystalball rspec data found."
...@@ -119,8 +127,8 @@ function rspec_paralellized_job() { ...@@ -119,8 +127,8 @@ function rspec_paralellized_job() {
local rspec_args="-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}" local rspec_args="-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}"
if [[ -n $RSPEC_MATCHING_TESTS_ENABLED ]]; then if [[ -n $RSPEC_TESTS_MAPPING_ENABLED ]]; then
tooling/bin/parallel_rspec --rspec_args "${rspec_args}" --filter tmp/matching_tests.txt tooling/bin/parallel_rspec --rspec_args "${rspec_args}" --filter "tmp/matching_tests.txt"
else else
tooling/bin/parallel_rspec --rspec_args "${rspec_args}" tooling/bin/parallel_rspec --rspec_args "${rspec_args}"
fi fi
......
...@@ -36,7 +36,7 @@ function install_gitlab_gem() { ...@@ -36,7 +36,7 @@ function install_gitlab_gem() {
} }
function install_tff_gem() { function install_tff_gem() {
gem install test_file_finder --version 0.1.0 gem install test_file_finder --version 0.1.1
} }
function run_timed_command() { function run_timed_command() {
......
...@@ -19,7 +19,12 @@ mr_iid = ENV.fetch('CI_MERGE_REQUEST_IID') ...@@ -19,7 +19,12 @@ mr_iid = ENV.fetch('CI_MERGE_REQUEST_IID')
mr_changes = Gitlab.merge_request_changes(mr_project_path, mr_iid) mr_changes = Gitlab.merge_request_changes(mr_project_path, mr_iid)
changed_files = mr_changes.changes.map { |change| change['new_path'] } changed_files = mr_changes.changes.map { |change| change['new_path'] }
mapping = TestFileFinder::Mapping.load('tests.yml') tff = TestFileFinder::FileFinder.new(paths: changed_files).tap do |file_finder|
test_files = TestFileFinder::FileFinder.new(paths: changed_files, mapping: mapping).test_files file_finder.use TestFileFinder::MappingStrategies::PatternMatching.load('tests.yml')
File.write(output_file, test_files.uniq.join(' ')) if ENV['RSPEC_TESTS_MAPPING_ENABLED']
file_finder.use TestFileFinder::MappingStrategies::DirectMatching.load_json(ENV['RSPEC_TESTS_MAPPING_PATH'])
end
end
File.write(output_file, tff.test_files.uniq.join(' '))
...@@ -60,7 +60,10 @@ module Tooling ...@@ -60,7 +60,10 @@ module Tooling
end end
def tests_to_run def tests_to_run
return node_tests if filter_tests.empty? if filter_tests.empty?
Knapsack.logger.info 'Running all node tests without filter'
return node_tests
end
@tests_to_run ||= node_tests & filter_tests @tests_to_run ||= node_tests & filter_tests
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment