gitlab_quality-test_tooling 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d279ff4333f75a706c86e3eced4a8b95c7612be05b740479ea032b8c7f49a62
4
- data.tar.gz: 92783c9acf4188c3dc781c097f3d25073fc70abab6c503a4c8802a06fda1cfb2
3
+ metadata.gz: c7119bececf62cc7d2d1aa0a025c4ee193535d74cfe62777e2e4b2ff88590687
4
+ data.tar.gz: 3e5a13270e9718b683d9554583a4b6b3c1d5f2a3bd5109477b51cdd931610600
5
5
  SHA512:
6
- metadata.gz: 0b0a7ab6ab95bb9356ddc3a5276a1b81083fe2056d102fc284d56e9b64890dcac5e76548f85dad95823bb0d1f8ceb1dc0ef0e4cb3e30ef7aad0ca11764a27b44
7
- data.tar.gz: c6b06b7040e97db27912113cab68d0161d51d311efe7ae7f5fb81b5f6ef55d7aec0a9d8df310f40f109b5a94d099e0e2be2e617a8119066f967e9efeaeb84a81
6
+ metadata.gz: c4cbc83f6bba83bd34ed42e619382778b5255dcc94df168d1cf0436c3b7f2d16c191ed9a6997db3d31e2cc030551a6359d30dc0ec380a8badc8e99d7373e070d
7
+ data.tar.gz: a87000a5dec45c065e46f836e9f1a7002d43aedf5c4eb11f59f9e69f321bad17537cc035269a7ce3adabef06e189b53cf69d46441bb7c5c245abd3b595a8df8b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (0.3.0)
4
+ gitlab_quality-test_tooling (0.4.1)
5
5
  activesupport (~> 6.1)
6
6
  gitlab (~> 4.18.0)
7
7
  http (~> 5.0)
data/README.md CHANGED
@@ -36,6 +36,8 @@ Usage: exe/generate-test-session [options]
36
36
  -t, --token TOKEN A valid access token with `api` scope and Reporter permission in PROJECT
37
37
  -c CI_PROJECT_TOKEN, A valid access token with `read_api` scope permission in current ENV["CI_PROJECT_ID"]
38
38
  --ci-project-token
39
+ -f ISSUE_URL_FILE, Output the created test session issue URL
40
+ --issue-url-file
39
41
  --dry-run Perform a dry-run (don't create or update issues or test cases)
40
42
  -v, --version Show the version
41
43
  -h, --help Show the usage
@@ -80,7 +82,7 @@ Usage: exe/relate-failure-issue [options]
80
82
  --max-diff-ratio MAX_DIFF_RATO
81
83
  Max stacktrace diff ratio for QA failure issues detection
82
84
  -p, --project PROJECT Can be an integer or a group/project string
83
- -t, --token TOKEN A valid access token with `api` scope and Reporter permission in PROJECT
85
+ -t, --token TOKEN A valid access token with `api` scope and Maintainer permission in PROJECT
84
86
  --system-log-files SYSTEM_LOG_FILES
85
87
  Include errors from system logs in failure issues
86
88
  --dry-run Perform a dry-run (don't create or update issues)
@@ -27,6 +27,10 @@ options = OptionParser.new do |opts|
27
27
  params[:ci_project_token] = ci_project_token
28
28
  end
29
29
 
30
+ opts.on('-f', '--issue-url-file ISSUE_URL_FILE', 'Output the created test session issue URL') do |issue_url_file|
31
+ params[:issue_url_file] = issue_url_file
32
+ end
33
+
30
34
  opts.on('--dry-run', "Perform a dry-run (don't create or update issues or test cases)") do
31
35
  params[:dry_run] = true
32
36
  end
@@ -46,8 +50,12 @@ options = OptionParser.new do |opts|
46
50
  opts.parse(ARGV)
47
51
  end
48
52
 
53
+ issue_url_file = params.delete(:issue_url_file)
54
+
49
55
  if params.any?
50
- GitlabQuality::TestTooling::Report::GenerateTestSession.new(**params).invoke!
56
+ issue_url = GitlabQuality::TestTooling::Report::GenerateTestSession.new(**params).invoke!
57
+
58
+ File.write(issue_url_file, issue_url) if issue_url_file && issue_url
51
59
  else
52
60
  puts options
53
61
  exit 1
@@ -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 Reporter permission in PROJECT') do |token|
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
- user = Gitlab.user
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
- abort_not_permitted
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 [Gitlab.issue(project, iid)].select(&select) if iid
50
+ break [client.issue(project, iid)].select(&select) if iid
54
51
 
55
- Gitlab.issues(project, options)
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
- Gitlab.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
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
- Gitlab.create_issue(project, title, attrs)
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
- Gitlab.edit_issue(project, iid, options)
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
- Gitlab.issue_notes(project, iid, order_by: 'created_at', sort: 'asc')&.auto_paginate
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
- Gitlab.create_issue_note(project, iid, note)
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
- Gitlab.edit_issue_note(project, issue_iid, note_id, note)
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
- Gitlab.add_note_to_issue_discussion_as_thread(project, iid, discussion_id, body: body)
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 = Gitlab.users(username: username)&.first
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
- Gitlab.upload_file(project, file_fullpath)
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 configure_gitlab_client
177
- Gitlab.configure do |config|
178
- config.endpoint = Runtime::Env.gitlab_api_base
179
- config.private_token = token
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 "You must have at least Reporter access to the project to use this feature."
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 update_issue_title(issue, test)
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
- warn(%(#{issue_type} title needs to be updated from '#{old_title}' to '#{new_title}'))
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
- gitlab.edit_issue(iid: issue.iid, options: { title: new_title, description: new_description })
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
- def assert_input_files!(files)
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
- issue, is_new = results_issue_project_reporter.get_related_issue(testcase, test)
60
+ result_issue, is_new = results_issue_project_reporter.get_related_issue(testcase, test)
74
61
 
75
- testcase_project_reporter.add_issue_link_to_testcase(testcase, issue, test) if is_new
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
- results_issue_project_reporter.update_issue(issue, test)
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 = update_issue_title(issue, test) if issue_title_needs_updating?(issue, test)
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 update_issue(issue, test)
30
+ def update_issue_labels(issue, test)
31
31
  new_labels = issue_labels(issue)
32
32
  new_labels |= ['Testcase Linked']
33
33
 
34
- labels_updated = update_labels(issue, test, new_labels)
35
- note_posted = note_status(issue, test)
34
+ update_labels(issue, test, new_labels)
35
+ end
36
36
 
37
- if labels_updated || note_posted
38
- puts "Issue updated: #{issue.web_url}"
39
- else
40
- puts "Test passed, no results issue update needed."
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 puts "No issue link found" unless results
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 = [":x: ~\"#{pipeline}::failed\""]
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
@@ -20,32 +20,31 @@ module GitlabQuality
20
20
  find_testcase(test) || create_issue(test)
21
21
  end
22
22
 
23
- def add_issue_link_to_testcase(testcase, issue, test)
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#{issue.web_url}") })
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 #{issue.web_url} link to test case #{test.testcase}"
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 "Test case labels updated." if update_labels(testcase, test)
34
- puts "Test case quarantine section updated." if update_quarantine_link(testcase, test)
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
- testcase = update_issue_title(testcase, test) if issue_title_needs_updating?(testcase, test)
43
+ if testcase_needs_updating?(testcase, test)
44
+ update_issue(testcase, test)
44
45
  else
45
- testcase = find_issue(test)
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")) if url&.include?('/-/issues/')
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 issue_title_needs_updating?(testcase, test)
89
- super || (!testcase.description.include?(execution_graph_section(test)) && !%w[canary production preprod
90
- release].include?(pipeline))
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 quarantine_link_needs_updating?(testcase, test)
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 update_quarantine_link(testcase, test)
104
- return unless quarantine_link_needs_updating?(testcase, test)
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.1"
6
6
  end
7
7
  end
@@ -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.3.0
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 00:00:00.000000000 Z
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/results_in_testcases.rb
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