blob: 1ab93b6dfbfe0deed526585b56dcceba1b1c6f6a (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# frozen_string_literal: true
require "pathname"
module QA
module Tools
module Ci
# Determine specific qa specs or paths to execute based on changes
class QaChanges
include Helpers
QA_PATTERN = %r{^qa/}.freeze
SPEC_PATTERN = %r{^qa/qa/specs/features/\S+_spec\.rb}.freeze
DEPENDENCY_PATTERN = Regexp.union(
/_VERSION/,
/Gemfile\.lock/,
/yarn\.lock/,
/Dockerfile\.assets/
)
def initialize(mr_diff, mr_labels, additional_group_spec_list)
@mr_diff = mr_diff
@mr_labels = mr_labels
@additional_group_spec_list = additional_group_spec_list
end
# Specific specs to run
#
# @return [String]
def qa_tests
return if mr_diff.empty? || dependency_changes
# make paths relative to qa directory
return changed_files&.map { |path| path.delete_prefix("qa/") }&.join(" ") if only_spec_changes?
return qa_spec_directories_for_devops_stage&.join(" ") if non_qa_changes? && mr_labels.any?
end
# Qa framework changes
#
# @return [Boolean]
def framework_changes?
return false if mr_diff.empty?
return false if only_spec_changes?
changed_files
# TODO: expand pattern to other non spec paths that shouldn't trigger full suite
.select { |file_path| file_path.match?(QA_PATTERN) && !file_path.match?(SPEC_PATTERN) }
.any?
end
def quarantine_changes?
return false if mr_diff.empty?
return false if mr_diff.any? { |change| change[:new_file] || change[:deleted_file] }
files_count = 0
specs_count = 0
quarantine_specs_count = 0
mr_diff.each do |change|
path = change[:path]
next if File.directory?(File.expand_path("../#{path}", QA::Runtime::Path.qa_root))
files_count += 1
next unless path.match?(SPEC_PATTERN) && path.end_with?('_spec.rb')
specs_count += 1
quarantine_specs_count += 1 if change[:diff].match?(/^\+.*,? quarantine:/)
end
return false if specs_count == 0
return true if quarantine_specs_count == specs_count && quarantine_specs_count == files_count
false
end
private
# @return [Array]
attr_reader :mr_diff
# @return [Array]
attr_reader :mr_labels
# @return [Hash<String, Array<String>>]
attr_reader :additional_group_spec_list
# Are the changed files only qa specs?
#
# @return [Boolean] whether the changes files are only qa specs
def only_spec_changes?
changed_files.all? { |file_path| file_path =~ SPEC_PATTERN }
end
# Are the changed files only outside the qa directory?
#
# @return [Boolean] whether the changes files are outside of qa directory
def non_qa_changes?
changed_files.none? { |file_path| file_path =~ QA_PATTERN }
end
# Extract devops stage from MR labels
#
# @return [String] a devops stage
def devops_stage_from_mr_labels
mr_labels.find { |label| label =~ /^devops::/ }&.delete_prefix('devops::')
end
# Extract group name from MR labels
#
# @return [String] a group name
def group_name_from_mr_labels
mr_labels.find { |label| label =~ /^group::/ }&.delete_prefix('group::')
end
# Get qa spec directories for devops stage
#
# @return [Array] qa spec directories
def qa_spec_directories_for_devops_stage
devops_stage = devops_stage_from_mr_labels
return unless devops_stage
spec_dirs = stage_specs(devops_stage)
grp_name = group_name_from_mr_labels
return spec_dirs if grp_name.nil?
additional_grp_specs = additional_group_spec_list[grp_name]
return spec_dirs if additional_grp_specs.nil?
spec_dirs + stage_specs(*additional_grp_specs)
end
# Changes to gitlab dependencies
#
# @return [Boolean]
def dependency_changes
changed_files.any? { |file| file.match?(DEPENDENCY_PATTERN) }
end
# Change files in merge request
#
# @return [Array<String>]
def changed_files
@changed_files ||= mr_diff.map { |change| change[:path] }
end
# Devops stage specs
#
# @param [Array<String>] devops_stages
# @return [Array]
def stage_specs(*devops_stages)
Dir.glob("qa/specs/**/*/").select { |dir| dir =~ %r{\d+_(#{devops_stages.join('|')})/$} }
end
end
end
end
end
|