gitlab_quality-test_tooling 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/exe/relate-failure-issue +1 -1
- data/lib/gitlab_quality/test_tooling/gitlab_issue_client.rb +38 -23
- data/lib/gitlab_quality/test_tooling/report/concerns/results_reporter.rb +16 -3
- data/lib/gitlab_quality/test_tooling/report/concerns/utils.rb +8 -8
- data/lib/gitlab_quality/test_tooling/report/report_results.rb +13 -17
- data/lib/gitlab_quality/test_tooling/report/results_in_issues.rb +23 -30
- data/lib/gitlab_quality/test_tooling/report/{results_in_testcases.rb → results_in_test_cases.rb} +19 -18
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- data/lib/gitlab_quality/test_tooling.rb +0 -4
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7119bececf62cc7d2d1aa0a025c4ee193535d74cfe62777e2e4b2ff88590687
|
4
|
+
data.tar.gz: 3e5a13270e9718b683d9554583a4b6b3c1d5f2a3bd5109477b51cdd931610600
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4cbc83f6bba83bd34ed42e619382778b5255dcc94df168d1cf0436c3b7f2d16c191ed9a6997db3d31e2cc030551a6359d30dc0ec380a8badc8e99d7373e070d
|
7
|
+
data.tar.gz: a87000a5dec45c065e46f836e9f1a7002d43aedf5c4eb11f59f9e69f321bad17537cc035269a7ce3adabef06e189b53cf69d46441bb7c5c245abd3b595a8df8b
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -82,7 +82,7 @@ Usage: exe/relate-failure-issue [options]
|
|
82
82
|
--max-diff-ratio MAX_DIFF_RATO
|
83
83
|
Max stacktrace diff ratio for QA failure issues detection
|
84
84
|
-p, --project PROJECT Can be an integer or a group/project string
|
85
|
-
-t, --token TOKEN A valid access token with `api` scope and
|
85
|
+
-t, --token TOKEN A valid access token with `api` scope and Maintainer permission in PROJECT
|
86
86
|
--system-log-files SYSTEM_LOG_FILES
|
87
87
|
Include errors from system logs in failure issues
|
88
88
|
--dry-run Perform a dry-run (don't create or update issues)
|
data/exe/relate-failure-issue
CHANGED
@@ -23,7 +23,7 @@ options = OptionParser.new do |opts|
|
|
23
23
|
params[:project] = project
|
24
24
|
end
|
25
25
|
|
26
|
-
opts.on('-t', '--token TOKEN', String, 'A valid access token with `api` scope and
|
26
|
+
opts.on('-t', '--token TOKEN', String, 'A valid access token with `api` scope and Maintainer permission in PROJECT') do |token|
|
27
27
|
params[:token] = token
|
28
28
|
end
|
29
29
|
|
@@ -31,28 +31,25 @@ module GitlabQuality
|
|
31
31
|
@token = token
|
32
32
|
@project = project
|
33
33
|
@retry_backoff = 0
|
34
|
-
|
35
|
-
configure_gitlab_client
|
36
34
|
end
|
37
35
|
|
38
36
|
def assert_user_permission!
|
39
37
|
handle_gitlab_client_exceptions do
|
40
|
-
|
41
|
-
member = Gitlab.team_member(project, user.id)
|
38
|
+
member = client.team_member(project, user.id)
|
42
39
|
|
43
|
-
abort_not_permitted if member.access_level < REPORTER_ACCESS_LEVEL
|
40
|
+
abort_not_permitted(member.access_level) if member.access_level < REPORTER_ACCESS_LEVEL
|
44
41
|
end
|
45
42
|
rescue Gitlab::Error::NotFound
|
46
|
-
|
43
|
+
abort_member_not_found(user)
|
47
44
|
end
|
48
45
|
|
49
46
|
def find_issues(iid: nil, options: {}, &select)
|
50
47
|
select ||= :itself
|
51
48
|
|
52
49
|
handle_gitlab_client_exceptions do
|
53
|
-
break [
|
50
|
+
break [client.issue(project, iid)].select(&select) if iid
|
54
51
|
|
55
|
-
|
52
|
+
client.issues(project, options)
|
56
53
|
.auto_paginate
|
57
54
|
.select(&select)
|
58
55
|
end
|
@@ -60,7 +57,7 @@ module GitlabQuality
|
|
60
57
|
|
61
58
|
def find_issue_discussions(iid:)
|
62
59
|
handle_gitlab_client_exceptions do
|
63
|
-
|
60
|
+
client.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
|
64
61
|
end
|
65
62
|
end
|
66
63
|
|
@@ -68,50 +65,50 @@ module GitlabQuality
|
|
68
65
|
attrs = { issue_type: issue_type, description: description, labels: labels }
|
69
66
|
|
70
67
|
handle_gitlab_client_exceptions do
|
71
|
-
|
68
|
+
client.create_issue(project, title, attrs)
|
72
69
|
end
|
73
70
|
end
|
74
71
|
|
75
72
|
def edit_issue(iid:, options: {})
|
76
73
|
handle_gitlab_client_exceptions do
|
77
|
-
|
74
|
+
client.edit_issue(project, iid, options)
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
81
78
|
def find_issue_notes(iid:)
|
82
79
|
handle_gitlab_client_exceptions do
|
83
|
-
|
80
|
+
client.issue_notes(project, iid, order_by: 'created_at', sort: 'asc')&.auto_paginate
|
84
81
|
end
|
85
82
|
end
|
86
83
|
|
87
84
|
def create_issue_note(iid:, note:)
|
88
85
|
handle_gitlab_client_exceptions do
|
89
|
-
|
86
|
+
client.create_issue_note(project, iid, note)
|
90
87
|
end
|
91
88
|
end
|
92
89
|
|
93
90
|
def edit_issue_note(issue_iid:, note_id:, note:)
|
94
91
|
handle_gitlab_client_exceptions do
|
95
|
-
|
92
|
+
client.edit_issue_note(project, issue_iid, note_id, note)
|
96
93
|
end
|
97
94
|
end
|
98
95
|
|
99
96
|
def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
|
100
97
|
handle_gitlab_client_exceptions do
|
101
|
-
|
98
|
+
client.add_note_to_issue_discussion_as_thread(project, iid, discussion_id, body: body)
|
102
99
|
end
|
103
100
|
end
|
104
101
|
|
105
102
|
def find_user_id(username:)
|
106
103
|
handle_gitlab_client_exceptions do
|
107
|
-
user =
|
104
|
+
user = client.users(username: username)&.first
|
108
105
|
user['id'] unless user.nil?
|
109
106
|
end
|
110
107
|
end
|
111
108
|
|
112
109
|
def upload_file(file_fullpath:)
|
113
110
|
ignore_gitlab_client_exceptions do
|
114
|
-
|
111
|
+
client.upload_file(project, file_fullpath)
|
115
112
|
end
|
116
113
|
end
|
117
114
|
|
@@ -173,15 +170,33 @@ module GitlabQuality
|
|
173
170
|
|
174
171
|
attr_reader :token, :project
|
175
172
|
|
176
|
-
def
|
177
|
-
Gitlab.
|
178
|
-
|
179
|
-
|
173
|
+
def client
|
174
|
+
@client ||= Gitlab.client(
|
175
|
+
endpoint: Runtime::Env.gitlab_api_base,
|
176
|
+
private_token: token
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
def user
|
181
|
+
return @user if defined?(@user)
|
182
|
+
|
183
|
+
@user ||= begin
|
184
|
+
client.user
|
185
|
+
rescue Gitlab::Error::NotFound
|
186
|
+
abort_user_not_found
|
180
187
|
end
|
181
188
|
end
|
182
189
|
|
183
|
-
def abort_not_permitted
|
184
|
-
abort "
|
190
|
+
def abort_not_permitted(access_level)
|
191
|
+
abort "#{user.username} must have at least Reporter access to the project '#{project}' to use this feature. Current access level: #{access_level}"
|
192
|
+
end
|
193
|
+
|
194
|
+
def abort_user_not_found
|
195
|
+
abort "User not found for given token."
|
196
|
+
end
|
197
|
+
|
198
|
+
def abort_member_not_found(user)
|
199
|
+
abort "#{user.username} must be a member of the '#{project}' project."
|
185
200
|
end
|
186
201
|
|
187
202
|
def warn_exception(error)
|
@@ -46,15 +46,28 @@ module GitlabQuality
|
|
46
46
|
labels << (test.failures.empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
49
|
+
def update_issue(issue, test)
|
50
|
+
update_params = {}
|
51
|
+
|
50
52
|
old_title = issue.title.strip
|
51
53
|
new_title = title_from_test(test)
|
52
54
|
|
53
|
-
|
55
|
+
if old_title != new_title
|
56
|
+
update_params[:title] = new_title
|
57
|
+
warn(%(#{issue_type} title needs to be updated from '#{old_title}' to '#{new_title}'))
|
58
|
+
end
|
54
59
|
|
60
|
+
old_description = issue.description
|
55
61
|
new_description = updated_description(issue, test)
|
56
62
|
|
57
|
-
|
63
|
+
if old_description != new_description
|
64
|
+
warn(%(#{issue_type} description needs to be updated from '#{old_description}' to '#{new_description}'))
|
65
|
+
update_params[:description] = new_description
|
66
|
+
end
|
67
|
+
|
68
|
+
return if update_params.empty?
|
69
|
+
|
70
|
+
gitlab.edit_issue(iid: issue.iid, options: update_params)
|
58
71
|
end
|
59
72
|
|
60
73
|
private
|
@@ -7,14 +7,6 @@ module GitlabQuality
|
|
7
7
|
module Utils
|
8
8
|
MAX_TITLE_LENGTH = 255
|
9
9
|
|
10
|
-
def partial_file_path(path)
|
11
|
-
path.match(/((ee|api|browser_ui).*)/i)[1]
|
12
|
-
end
|
13
|
-
|
14
|
-
def new_issue_title(test)
|
15
|
-
"#{partial_file_path(test.file)} | #{search_safe(test.name)}".strip
|
16
|
-
end
|
17
|
-
|
18
10
|
def title_from_test(test)
|
19
11
|
title = new_issue_title(test)
|
20
12
|
|
@@ -23,6 +15,14 @@ module GitlabQuality
|
|
23
15
|
"#{title[0...MAX_TITLE_LENGTH - 3]}..."
|
24
16
|
end
|
25
17
|
|
18
|
+
def new_issue_title(test)
|
19
|
+
"#{partial_file_path(test.file)} | #{search_safe(test.name)}".strip
|
20
|
+
end
|
21
|
+
|
22
|
+
def partial_file_path(path)
|
23
|
+
path.match(/((ee|api|browser_ui).*)/i)[1]
|
24
|
+
end
|
25
|
+
|
26
26
|
def search_safe(value)
|
27
27
|
value.delete('"')
|
28
28
|
end
|
@@ -24,22 +24,12 @@ module GitlabQuality
|
|
24
24
|
@gitlab = testcase_project_reporter.gitlab
|
25
25
|
end
|
26
26
|
|
27
|
-
def invoke!
|
28
|
-
validate_input!
|
29
|
-
|
30
|
-
run!
|
31
|
-
end
|
32
|
-
|
33
27
|
def validate_input!
|
34
28
|
assert_input_files!(files)
|
35
29
|
gitlab.assert_user_permission!
|
36
30
|
end
|
37
31
|
|
38
|
-
|
39
|
-
return if Dir.glob(files).any?
|
40
|
-
|
41
|
-
abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
|
42
|
-
end
|
32
|
+
private
|
43
33
|
|
44
34
|
# rubocop:disable Metrics/AbcSize
|
45
35
|
def run!
|
@@ -60,9 +50,6 @@ module GitlabQuality
|
|
60
50
|
test_results.write
|
61
51
|
end
|
62
52
|
end
|
63
|
-
# rubocop:enable Metrics/AbcSize
|
64
|
-
|
65
|
-
private
|
66
53
|
|
67
54
|
def report_test(test)
|
68
55
|
testcase = testcase_project_reporter.find_or_create_testcase(test)
|
@@ -70,13 +57,22 @@ module GitlabQuality
|
|
70
57
|
# This updates the URL to a valid test case link.
|
71
58
|
test.testcase = testcase.web_url.sub('/issues/', '/quality/test_cases/')
|
72
59
|
|
73
|
-
|
60
|
+
result_issue, is_new = results_issue_project_reporter.get_related_issue(testcase, test)
|
74
61
|
|
75
|
-
testcase_project_reporter.
|
62
|
+
testcase_project_reporter.add_result_issue_link_to_testcase(testcase, result_issue, test) if is_new
|
76
63
|
|
77
64
|
testcase_project_reporter.update_testcase(testcase, test)
|
78
|
-
|
65
|
+
|
66
|
+
labels_updated = results_issue_project_reporter.update_issue_labels(result_issue, test)
|
67
|
+
note_posted = results_issue_project_reporter.post_note(result_issue, test)
|
68
|
+
|
69
|
+
if labels_updated || note_posted
|
70
|
+
puts "Issue updated: #{result_issue.web_url}"
|
71
|
+
else
|
72
|
+
puts "Test passed, no results issue update needed."
|
73
|
+
end
|
79
74
|
end
|
75
|
+
# rubocop:enable Metrics/AbcSize
|
80
76
|
end
|
81
77
|
end
|
82
78
|
end
|
@@ -17,9 +17,9 @@ module GitlabQuality
|
|
17
17
|
is_new = false
|
18
18
|
|
19
19
|
if issue
|
20
|
-
issue =
|
20
|
+
issue = update_issue(issue, test) if issue_title_needs_updating?(issue, test)
|
21
21
|
else
|
22
|
-
puts "No valid issue link found"
|
22
|
+
puts "No valid issue link found."
|
23
23
|
issue = find_or_create_results_issue(test)
|
24
24
|
is_new = true
|
25
25
|
end
|
@@ -27,18 +27,30 @@ module GitlabQuality
|
|
27
27
|
[issue, is_new]
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
30
|
+
def update_issue_labels(issue, test)
|
31
31
|
new_labels = issue_labels(issue)
|
32
32
|
new_labels |= ['Testcase Linked']
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
update_labels(issue, test, new_labels)
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def post_note(issue, test)
|
38
|
+
return false if test.skipped
|
39
|
+
return false if test.failures.empty?
|
40
|
+
|
41
|
+
note = note_content(test)
|
42
|
+
|
43
|
+
gitlab.find_issue_discussions(iid: issue.iid).each do |discussion|
|
44
|
+
next unless new_note_matches_discussion?(note, discussion)
|
45
|
+
|
46
|
+
gitlab.add_note_to_issue_discussion_as_thread(
|
47
|
+
iid: issue.iid,
|
48
|
+
discussion_id: discussion.id,
|
49
|
+
body: failure_summary)
|
50
|
+
return true
|
41
51
|
end
|
52
|
+
|
53
|
+
gitlab.create_issue_note(iid: issue.iid, note: note)
|
42
54
|
end
|
43
55
|
|
44
56
|
private
|
@@ -58,32 +70,13 @@ module GitlabQuality
|
|
58
70
|
def issue_iid_from_testcase(testcase)
|
59
71
|
results = testcase.description.partition(TEST_CASE_RESULTS_SECTION_TEMPLATE).last if testcase.description.include?(TEST_CASE_RESULTS_SECTION_TEMPLATE)
|
60
72
|
|
61
|
-
return
|
73
|
+
return unless results
|
62
74
|
|
63
75
|
issue_iid = results.split('/').last
|
64
76
|
|
65
77
|
issue_iid&.to_i
|
66
78
|
end
|
67
79
|
|
68
|
-
def note_status(issue, test)
|
69
|
-
return false if test.skipped
|
70
|
-
return false if test.failures.empty?
|
71
|
-
|
72
|
-
note = note_content(test)
|
73
|
-
|
74
|
-
gitlab.find_issue_discussions(iid: issue.iid).each do |discussion|
|
75
|
-
if new_note_matches_discussion?(
|
76
|
-
note, discussion)
|
77
|
-
return gitlab.add_note_to_issue_discussion_as_thread(iid: issue.iid, discussion_id: discussion.id,
|
78
|
-
body: failure_summary)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
gitlab.create_issue_note(iid: issue.iid, note: note)
|
83
|
-
|
84
|
-
true
|
85
|
-
end
|
86
|
-
|
87
80
|
def note_content(test)
|
88
81
|
errors = test.failures.each_with_object([]) do |failure, text|
|
89
82
|
text << <<~TEXT
|
@@ -103,7 +96,7 @@ module GitlabQuality
|
|
103
96
|
end
|
104
97
|
|
105
98
|
def failure_summary
|
106
|
-
summary = [
|
99
|
+
summary = [%(:x: ~"#{pipeline}::failed")]
|
107
100
|
summary << "in job `#{Runtime::Env.ci_job_name}` in #{Runtime::Env.ci_job_url}"
|
108
101
|
summary.join(' ')
|
109
102
|
end
|
data/lib/gitlab_quality/test_tooling/report/{results_in_testcases.rb → results_in_test_cases.rb}
RENAMED
@@ -20,32 +20,31 @@ module GitlabQuality
|
|
20
20
|
find_testcase(test) || create_issue(test)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def add_result_issue_link_to_testcase(testcase, result_issue, test)
|
24
24
|
results_section = testcase.description.include?(TEST_CASE_RESULTS_SECTION_TEMPLATE) ? '' : TEST_CASE_RESULTS_SECTION_TEMPLATE
|
25
25
|
|
26
26
|
gitlab.edit_issue(iid: testcase.iid,
|
27
|
-
options: { description: (testcase.description + results_section + "\n\n#{
|
27
|
+
options: { description: (testcase.description + results_section + "\n\n#{result_issue.web_url}") })
|
28
28
|
# We are using test.testcase for the url here instead of testcase.web_url since it has the updated test case path
|
29
|
-
puts "Added results issue #{
|
29
|
+
puts "Added results issue #{result_issue.web_url} link to test case #{test.testcase}"
|
30
30
|
end
|
31
31
|
|
32
32
|
def update_testcase(testcase, test)
|
33
|
-
puts "
|
34
|
-
puts "
|
33
|
+
puts "Labels updated for test case #{test.testcase}." if update_labels(testcase, test)
|
34
|
+
puts "Quarantine section updated for test case #{test.testcase}." if update_quarantine_section(testcase, test)
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
39
|
def find_testcase(test)
|
40
|
-
testcase = find_testcase_by_iid(test)
|
40
|
+
testcase = find_testcase_by_iid(test) || find_issue(test)
|
41
|
+
return unless testcase
|
41
42
|
|
42
|
-
if testcase
|
43
|
-
|
43
|
+
if testcase_needs_updating?(testcase, test)
|
44
|
+
update_issue(testcase, test)
|
44
45
|
else
|
45
|
-
testcase
|
46
|
+
testcase
|
46
47
|
end
|
47
|
-
|
48
|
-
testcase
|
49
48
|
end
|
50
49
|
|
51
50
|
def find_testcase_by_iid(test)
|
@@ -57,7 +56,7 @@ module GitlabQuality
|
|
57
56
|
end
|
58
57
|
|
59
58
|
def testcase_iid_from_url(url)
|
60
|
-
return warn(%(\nPlease update #{url} to test case url
|
59
|
+
return warn(%(\nPlease update #{url} to test case url)) if url&.include?('/-/issues/')
|
61
60
|
|
62
61
|
url && url.split('/').last.to_i
|
63
62
|
end
|
@@ -85,12 +84,14 @@ module GitlabQuality
|
|
85
84
|
"#{new_issue_description(test)}\n\n#{historical_results_section}"
|
86
85
|
end
|
87
86
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
87
|
+
def testcase_needs_updating?(testcase, test)
|
88
|
+
return false if %w[canary production preprod release].include?(pipeline)
|
89
|
+
return true if issue_title_needs_updating?(testcase, test)
|
90
|
+
|
91
|
+
!testcase.description.include?(execution_graph_section(test))
|
91
92
|
end
|
92
93
|
|
93
|
-
def
|
94
|
+
def quarantine_section_needs_updating?(testcase, test)
|
94
95
|
if test.quarantine? && test.quarantine_issue
|
95
96
|
return false if testcase.description.include?(test.quarantine_issue)
|
96
97
|
else
|
@@ -100,8 +101,8 @@ module GitlabQuality
|
|
100
101
|
true
|
101
102
|
end
|
102
103
|
|
103
|
-
def
|
104
|
-
return unless
|
104
|
+
def update_quarantine_section(testcase, test)
|
105
|
+
return unless quarantine_section_needs_updating?(testcase, test)
|
105
106
|
|
106
107
|
new_description = updated_description(testcase, test)
|
107
108
|
|
@@ -10,10 +10,6 @@ module GitlabQuality
|
|
10
10
|
loader.push_dir("#{__dir__}/test_tooling", namespace: GitlabQuality::TestTooling)
|
11
11
|
loader.ignore("#{__dir__}/test_tooling/version.rb")
|
12
12
|
|
13
|
-
loader.inflector.inflect(
|
14
|
-
'results_in_testcases' => 'ResultsInTestCases'
|
15
|
-
)
|
16
|
-
|
17
13
|
loader.setup
|
18
14
|
end
|
19
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab_quality-test_tooling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab Quality
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: climate_control
|
@@ -353,7 +353,7 @@ files:
|
|
353
353
|
- lib/gitlab_quality/test_tooling/report/report_as_issue.rb
|
354
354
|
- lib/gitlab_quality/test_tooling/report/report_results.rb
|
355
355
|
- lib/gitlab_quality/test_tooling/report/results_in_issues.rb
|
356
|
-
- lib/gitlab_quality/test_tooling/report/
|
356
|
+
- lib/gitlab_quality/test_tooling/report/results_in_test_cases.rb
|
357
357
|
- lib/gitlab_quality/test_tooling/report/update_screenshot_path.rb
|
358
358
|
- lib/gitlab_quality/test_tooling/runtime/env.rb
|
359
359
|
- lib/gitlab_quality/test_tooling/runtime/logger.rb
|