Commit a6fc61a1 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch '219262-run-as-if-foss-jobs-based-on-modified-files' into 'master'

Run foss impact tests for modified files in `as-if-foss` mode

Closes #219262

See merge request gitlab-org/gitlab!33243
parents 8b8785c9 b8b973fb
......@@ -92,3 +92,4 @@ webpack-dev-server.json
/.nvimrc
.solargraph.yml
apollo.config.js
/tmp/matching_foss_tests.txt
......@@ -324,3 +324,25 @@ db:rollback geo:
- bundle exec rake geo:db:migrate
# EE: default refs (MRs, master, schedules) jobs #
##################################################
##################################################
# EE: Canonical MR pipelines
rspec foss-impact:
extends:
- .rspec-base
- .as-if-foss
- .rails:rules:ee-mr-only
- .use-pg11
script:
- install_gitlab_gem
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source scripts/rspec_helpers.sh
- tooling/bin/find_foss_tests tmp/matching_foss_tests.txt
- rspec_simple_job "--tag ~quarantine --tag ~geo --tag ~level:migration $(cat tmp/matching_foss_tests.txt)"
artifacts:
expire_in: 7d
paths:
- tmp/matching_foss_tests.txt
# EE: Merge Request pipelines
##################################################
......@@ -446,6 +446,15 @@
- <<: *if-master-refs
changes: *code-backstage-patterns
.rails:rules:ee-mr-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-backstage-patterns
.rails:rules:downtime_check:
rules:
- <<: *if-merge-request
......
......@@ -13,4 +13,6 @@ require 'active_support/all'
ActiveSupport::Dependencies.autoload_paths << 'lib'
ActiveSupport::Dependencies.autoload_paths << 'ee/lib'
ActiveSupport::Dependencies.autoload_paths << 'tooling/lib'
ActiveSupport::XmlMini.backend = 'Nokogiri'
# frozen_string_literal: true
require 'fast_spec_helper'
describe Tooling::TestFileFinder do
subject { Tooling::TestFileFinder.new(file) }
describe '#test_files' do
context 'when given non .rb files' do
let(:file) { 'app/assets/images/emoji.png' }
it 'does not return a test file' do
expect(subject.test_files).to be_empty
end
end
context 'when given file in app/' do
let(:file) { 'app/finders/admin/projects_finder.rb' }
it 'returns the matching app spec file' do
expect(subject.test_files).to contain_exactly('spec/finders/admin/projects_finder_spec.rb')
end
end
context 'when given file in lib/' do
let(:file) { 'lib/banzai/color_parser.rb' }
it 'returns the matching app spec file' do
expect(subject.test_files).to contain_exactly('spec/lib/banzai/color_parser_spec.rb')
end
end
context 'when given a file in tooling/' do
let(:file) { 'tooling/lib/quality/test_file_finder.rb' }
it 'returns the matching tooling test' do
expect(subject.test_files).to contain_exactly('spec/tooling/lib/quality/test_file_finder_spec.rb')
end
end
context 'when given a test file' do
let(:file) { 'spec/lib/banzai/color_parser_spec.rb' }
it 'returns the matching test file itself' do
expect(subject.test_files).to contain_exactly('spec/lib/banzai/color_parser_spec.rb')
end
end
context 'when given an app file in ee/' do
let(:file) { 'ee/app/models/analytics/cycle_analytics/group_level.rb' }
it 'returns the matching ee/ test file' do
expect(subject.test_files).to contain_exactly('ee/spec/models/analytics/cycle_analytics/group_level_spec.rb')
end
end
context 'when given a module file in ee/' do
let(:file) { 'ee/app/models/ee/user.rb' }
it 'returns the matching ee/ module test file and the ee/ model test file' do
test_files = ['ee/spec/models/ee/user_spec.rb', 'spec/app/models/user_spec.rb']
expect(subject.test_files).to contain_exactly(*test_files)
end
end
context 'when given a lib file in ee/' do
let(:file) { 'ee/lib/flipper_session.rb' }
it 'returns the matching ee/ lib test file' do
expect(subject.test_files).to contain_exactly('ee/spec/lib/flipper_session_spec.rb')
end
end
context 'when given a test file in ee/' do
let(:file) { 'ee/spec/models/container_registry/event_spec.rb' }
it 'returns the test file itself' do
expect(subject.test_files).to contain_exactly('ee/spec/models/container_registry/event_spec.rb')
end
end
context 'when given a module test file in ee/' do
let(:file) { 'ee/spec/models/ee/appearance_spec.rb' }
it 'returns the matching module test file itself and the corresponding spec model test file' do
test_files = ['ee/spec/models/ee/appearance_spec.rb', 'spec/models/appearance_spec.rb']
expect(subject.test_files).to contain_exactly(*test_files)
end
end
context 'with foss_test_only: true' do
subject { Tooling::TestFileFinder.new(file, foss_test_only: true) }
context 'when given a module file in ee/' do
let(:file) { 'ee/app/models/ee/user.rb' }
it 'returns only the corresponding spec model test file in foss' do
expect(subject.test_files).to contain_exactly('spec/app/models/user_spec.rb')
end
end
context 'when given an app file in ee/' do
let(:file) { 'ee/app/models/approval.rb' }
it 'returns no test file in foss' do
expect(subject.test_files).to be_empty
end
end
end
end
end
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative '../../lib/gitlab/popen'
require_relative '../lib/tooling/test_file_finder'
require 'gitlab'
gitlab_token = ENV.fetch('DANGER_GITLAB_API_TOKEN', '')
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = gitlab_token
end
output_file = ARGV.shift
mr_project_path = ENV.fetch('CI_MERGE_REQUEST_PROJECT_PATH')
mr_iid = ENV.fetch('CI_MERGE_REQUEST_IID')
mr_changes = Gitlab.merge_request_changes(mr_project_path, mr_iid)
changed_files = mr_changes.changes.map { |change| change['new_path'] }
tests_to_run = changed_files.flat_map do |file|
test_files = Tooling::TestFileFinder.new(file, foss_test_only: true).test_files
test_files.select { |f| File.exist?(f) }
end
File.write(output_file, tests_to_run.uniq.join(' '))
# frozen_string_literal: true
require 'ostruct'
require 'set'
module Tooling
class TestFileFinder
RUBY_EXTENSION = '.rb'
EE_PREFIX = 'ee/'
def initialize(file, foss_test_only: false)
@file = file
@foss_test_only = foss_test_only
@result = Set.new
end
def test_files
contexts = [ee_context, foss_context]
contexts.flat_map do |context|
match_test_files_for(context)
end
result.to_a
end
private
attr_reader :file, :foss_test_only, :result
def ee_context
OpenStruct.new.tap do |ee|
ee.app = %r{^#{EE_PREFIX}app/(.+)\.rb$} unless foss_test_only
ee.lib = %r{^#{EE_PREFIX}lib/(.+)\.rb$} unless foss_test_only
ee.spec = %r{^#{EE_PREFIX}spec/(.+)_spec.rb$} unless foss_test_only
ee.spec_dir = "#{EE_PREFIX}spec" unless foss_test_only
ee.ee_modules = %r{^#{EE_PREFIX}(?!spec)(.*\/)ee/(.+)\.rb$}
ee.ee_module_spec = %r{^#{EE_PREFIX}spec/(.*\/)ee/(.+)\.rb$}
ee.foss_spec_dir = 'spec'
end
end
def foss_context
OpenStruct.new.tap do |foss|
foss.app = %r{^app/(.+)\.rb$}
foss.lib = %r{^lib/(.+)\.rb$}
foss.tooling = %r{^(tooling/lib/.+)\.rb$}
foss.spec = %r{^spec/(.+)_spec.rb$}
foss.spec_dir = 'spec'
end
end
def match_test_files_for(context)
if (match = context.app&.match(file))
result << "#{context.spec_dir}/#{match[1]}_spec.rb"
end
if (match = context.lib&.match(file))
result << "#{context.spec_dir}/lib/#{match[1]}_spec.rb"
end
if (match = context.tooling&.match(file))
result << "#{context.spec_dir}/#{match[1]}_spec.rb"
end
if context.spec&.match(file)
result << file
end
if (match = context.ee_modules&.match(file))
result << "#{context.foss_spec_dir}/#{match[1]}#{match[2]}_spec.rb"
end
if (match = context.ee_module_spec&.match(file))
result << "#{context.foss_spec_dir}/#{match[1]}#{match[2]}.rb"
end
end
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