gitlab_quality-test_tooling 0.9.3 → 1.0.0

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: fa16a309741761cbce53a73d541cef1c6bb587cd790492cddc02face87e4338b
4
- data.tar.gz: 9cdba5b6c53c83adba07ce9cc0316568b4e8137955fe46faef7705b4b577a5d9
3
+ metadata.gz: 989fdbc7beb98d23c28a6df0918b48db36962183f71e931d90c1ef70cb99b918
4
+ data.tar.gz: 5edeaf64ee7042db858fdec5cd9a434a449f530abeef5a03eafbec873cabf0f4
5
5
  SHA512:
6
- metadata.gz: dbb5472b2c4a5d70b26806c2467faae4a8b6770a2cf0ddc61f27e2c082f51459f5f8f7a4d640ec93ee3a8da0a126f5effdbae174d7aaf71d2054eddee0854529
7
- data.tar.gz: 98bdef30f3db77c15256ed2ef02e9b3550d8ed6d653e932f0550b35ba3b4f99bd899cedd518dd9a3e0e422ad07e232b2ee1eb1a3abfa2713927312a788c17ad5
6
+ metadata.gz: 936dc55126b3e217b38a43cb7b5b6810bc01f6bbc74d8713414818422130a9158d779941a6f49c11a4ca7e290922cf8c53c19717d1164a0b55e9bc4ee1481aa9
7
+ data.tar.gz: ebe5967dc286b06b72ed8efba01d3c713019e7e89c8b3bf30d26f6567832a4f939b0e4e15d39020acfa959352b3200a996562eb2b3bf43a40009ad6f4c96259b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (0.9.3)
4
+ gitlab_quality-test_tooling (1.0.0)
5
5
  activesupport (>= 6.1, < 7.1)
6
6
  gitlab (~> 4.19)
7
7
  http (~> 5.0)
data/README.md CHANGED
@@ -128,11 +128,28 @@ Purpose: Create slow test issues from JSON RSpec report files
128
128
  Usage: exe/slow-test-issue [options]
129
129
  -i, --input-files INPUT_FILES JSON RSpec report files JSON
130
130
  -p, --project PROJECT Can be an integer or a group/project string
131
+ -t, --token TOKEN A valid access token with `api` scope and Maintainer permission in PROJECT
132
+ --dry-run Perform a dry-run (don't create note)
133
+ -v, --version Show the version
134
+ -h, --help Show the usage
135
+ ```
136
+
137
+ ### `slow-test-merge-request-report-note`
138
+
139
+ ```shell
140
+ $ exe/slow-test-merge-request-report-note -h
141
+ Purpose: Create slow test note on merge requests from JSON RSpec report files
142
+ Usage: exe/slow-test-merge-request-report-note [options]
143
+ -i, --input-files INPUT_FILES JSON RSpec report files JSON
144
+ --project PROJECT Can be an integer or a group/project string
145
+ -m MERGE_REQUEST_IID, An integer merge request IID
146
+ --merge_request_iid
131
147
  -t, --token TOKEN A valid access token with `api` scope and Maintainer permission in PROJECT
132
148
  --dry-run Perform a dry-run (don't create issues)
133
149
  -v, --version Show the version
134
150
  -h, --help Show the usage
135
151
  ```
152
+
136
153
  ## Development
137
154
 
138
155
  ### Initial setup
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "optparse"
6
+
7
+ require_relative "../lib/gitlab_quality/test_tooling"
8
+
9
+ params = {}
10
+
11
+ options = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
13
+
14
+ opts.on('-i', '--input-files INPUT_FILES', String, 'JSON RSpec report files JSON') do |input_files|
15
+ params[:input_files] = input_files
16
+ end
17
+
18
+ opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
19
+ params[:project] = project
20
+ end
21
+
22
+ opts.on('-m', '--merge_request_iid MERGE_REQUEST_IID', String, 'An integer merge request IID') do |merge_request_iid|
23
+ params[:merge_request_iid] = merge_request_iid
24
+ end
25
+
26
+ opts.on('-t', '--token TOKEN', String, 'A valid access token with `api` scope and Maintainer permission in PROJECT') do |token|
27
+ params[:token] = token
28
+ end
29
+
30
+ opts.on('--dry-run', "Perform a dry-run (don't create note)") do
31
+ params[:dry_run] = true
32
+ end
33
+
34
+ opts.on_tail('-v', '--version', 'Show the version') do
35
+ require_relative "../lib/gitlab_quality/test_tooling/version"
36
+ puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
37
+ exit
38
+ end
39
+
40
+ opts.on_tail('-h', '--help', 'Show the usage') do
41
+ puts "Purpose: Create slow test note on merge requests from JSON RSpec report files"
42
+ puts opts
43
+ exit
44
+ end
45
+
46
+ opts.parse(ARGV)
47
+ end
48
+
49
+ if params.any?
50
+ GitlabQuality::TestTooling::Report::MergeRequestSlowTestsReport.new(**params).invoke!
51
+ else
52
+ puts options
53
+ exit 1
54
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gitlab'
4
+
5
+ module GitlabQuality
6
+ module TestTooling
7
+ module GitlabClient
8
+ class MergeRequest
9
+ def initialize(token:, project:, merge_request_iid:)
10
+ @token = token
11
+ @project = project
12
+ @merge_request_iid = merge_request_iid
13
+ end
14
+
15
+ def find_merge_request
16
+ client.merge_request_changes(project, merge_request_iid)
17
+ end
18
+
19
+ def merge_request_changed_files
20
+ find_merge_request["changes"].map do |change|
21
+ change["new_path"]
22
+ end
23
+ end
24
+
25
+ def find_note(body:)
26
+ client.merge_request_notes(project, merge_request_iid, per_page: 100).auto_paginate.find do |mr_note|
27
+ mr_note['body'] =~ /#{body}/
28
+ end
29
+ end
30
+
31
+ def create_note(note:)
32
+ client.create_merge_request_note(project, merge_request_iid, note)
33
+ end
34
+
35
+ def update_note(id:, note:)
36
+ client.edit_merge_request_note(project, merge_request_iid, id, note)
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :project, :token, :merge_request_iid
42
+
43
+ def client
44
+ @client ||= Gitlab.client(
45
+ endpoint: Runtime::Env.gitlab_api_base,
46
+ private_token: token
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module GitlabClient
6
+ class MergeRequestDryClient < MergeRequest
7
+ def find_merge_request
8
+ puts "Finding merge_request_id #{merge_request_iid}"
9
+ puts "project: #{project}"
10
+ end
11
+
12
+ def merge_request_changed_files
13
+ puts "Changed files for #{merge_request_iid}"
14
+ []
15
+ end
16
+
17
+ def find_note(body:)
18
+ puts "Find note for #{merge_request_iid} with body: #{body}"
19
+ end
20
+
21
+ def create_note(note:)
22
+ puts "The following note would have been created with body: #{note}"
23
+ end
24
+
25
+ def update_note(id:, note:)
26
+ puts "The following note would have been update id: #{id} with body: #{note}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -39,9 +39,7 @@ module GitlabQuality
39
39
 
40
40
  def fetch_group_sets(product_group)
41
41
  @group_sets = @stage_sets.select do |user|
42
- user['role'].include?(product_group.split("_").map do |word|
43
- word == 'and' ? word : word.capitalize
44
- end.join(" "))
42
+ user['role'].downcase.tr(' ', '_').include?(product_group)
45
43
  end
46
44
  end
47
45
  end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module Report
6
+ class MergeRequestSlowTestsReport
7
+ SLOW_TEST_MESSAGE = '<!-- slow-test -->'
8
+ SLOW_TEST_LABEL = '/label ~"rspec:slow test detected"'
9
+
10
+ def initialize(token:, input_files:, merge_request_iid:, project: nil, dry_run: false, **_kwargs)
11
+ @project = project
12
+ @gitlab_merge_request = (dry_run ? GitlabClient::MergeRequestDryClient : GitlabClient::MergeRequest).new(token: token, project: project,
13
+ merge_request_iid: merge_request_iid)
14
+ @files = Array(input_files)
15
+ @merge_request_iid = merge_request_iid
16
+ end
17
+
18
+ def invoke!
19
+ validate_input!
20
+
21
+ run!
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :gitlab_merge_request, :files, :project, :merge_request_iid
27
+
28
+ def run!
29
+ puts "Reporting slow tests in MR #{merge_request_iid}"
30
+
31
+ TestResults::Builder.new(files).test_results_per_file do |test_results|
32
+ puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
33
+
34
+ upsert_mr_note(slow_related_tests(slow_tests(test_results))) if should_create_mr_note?(test_results)
35
+ end
36
+ end
37
+
38
+ def should_create_mr_note?(test_results)
39
+ gitlab_merge_request.merge_request_changed_files.any? &&
40
+ slow_tests(test_results).any? &&
41
+ slow_related_tests(slow_tests(test_results)).any?
42
+ end
43
+
44
+ def slow_related_tests(slow_test_results)
45
+ slow_test_results.select do |slow_test_result|
46
+ base_file = slow_test_result.file.split('/').last.gsub('_spec.rb', '')
47
+
48
+ merge_request_changed_files.any? { |change| change =~ /#{base_file}/ }
49
+ end
50
+ end
51
+
52
+ def merge_request_changed_files
53
+ @merge_request_changed_files ||= gitlab_merge_request.merge_request_changed_files
54
+ end
55
+
56
+ def slow_tests(test_results)
57
+ test_results.select(&:slow_test?)
58
+ end
59
+
60
+ def note_header
61
+ [
62
+ SLOW_TEST_MESSAGE,
63
+ SLOW_TEST_LABEL,
64
+ ':snail: Slow tests detected in this merge request, might be related with changed RSpec files.',
65
+ '| Job | File | Name | Duration | Expected duration |',
66
+ '| --- | --- | --- | --- | --- |'
67
+ ]
68
+ end
69
+
70
+ def slow_test_rows(slow_test)
71
+ rows = []
72
+
73
+ slow_test.each do |test|
74
+ rows << slow_test_table_row(test)
75
+ end
76
+
77
+ rows
78
+ end
79
+
80
+ def build_note(slow_test)
81
+ rows = note_header + slow_test_rows(slow_test)
82
+
83
+ rows.join("\n")
84
+ end
85
+
86
+ def note_comment_includes_slow_test?(gitlab_note, slow_test)
87
+ gitlab_note.include?("[##{slow_test.ci_job_id}](#{slow_test.ci_job_url}) | #{slow_test.test_file_link}")
88
+ end
89
+
90
+ def slow_test_table_row(slow_test)
91
+ row = [
92
+ "[##{slow_test.ci_job_id}](#{slow_test.ci_job_url})",
93
+ slow_test.test_file_link,
94
+ slow_test.name,
95
+ "#{slow_test.run_time} s",
96
+ "< #{slow_test.max_duration_for_test} s"
97
+ ]
98
+
99
+ "| #{row.join(' | ')} |"
100
+ end
101
+
102
+ def add_slow_test_rows(gitlab_note, slow_tests)
103
+ slow_tests.each do |slow_test|
104
+ gitlab_note += "\n#{slow_test_table_row(slow_test)}" unless note_comment_includes_slow_test?(gitlab_note, slow_test)
105
+ end
106
+
107
+ gitlab_note
108
+ end
109
+
110
+ def upsert_mr_note(slow_tests)
111
+ existing_note = gitlab_merge_request.find_note(body: SLOW_TEST_MESSAGE)
112
+
113
+ if existing_note
114
+ puts "Update note for merge request: #{merge_request_iid}"
115
+
116
+ up_to_date_note = add_slow_test_rows(existing_note.body, slow_tests)
117
+
118
+ gitlab_merge_request.update_note(id: existing_note['id'], note: up_to_date_note) if existing_note.body != up_to_date_note
119
+ else
120
+ up_to_date_note = build_note(slow_tests)
121
+
122
+ puts "Create note for merge request: #{merge_request_iid}"
123
+
124
+ gitlab_merge_request.create_note(note: up_to_date_note)
125
+ end
126
+ end
127
+
128
+ def validate_input!
129
+ assert_project!
130
+ assert_input_files!(files)
131
+ end
132
+
133
+ def assert_project!
134
+ return if project
135
+
136
+ abort "Please provide a valid project ID or path with the `-p/--project` option!"
137
+ end
138
+
139
+ def assert_input_files!(files)
140
+ return if Dir.glob(files).any?
141
+
142
+ abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -8,19 +8,6 @@ module GitlabQuality
8
8
  class ReportAsIssue
9
9
  include Concerns::Utils
10
10
 
11
- FILE_BASE_URL = "https://gitlab.com/gitlab-org/gitlab/-/blob/master/"
12
-
13
- OTHER_TESTS_MAX_DURATION = 45.40 # seconds
14
-
15
- TestLevelSpecification = Struct.new(:regex, :max_duration)
16
-
17
- TEST_LEVEL_SPECIFICATIONS = [
18
- TestLevelSpecification.new(%r{spec/features/}, 50.13),
19
- TestLevelSpecification.new(%r{spec/(controllers|requests)/}, 19.20),
20
- TestLevelSpecification.new(%r{spec/lib/}, 27.12),
21
- TestLevelSpecification.new(%r{qa/specs/features/}, 240)
22
- ].freeze
23
-
24
11
  def initialize(token:, input_files:, project: nil, dry_run: false, **_kwargs)
25
12
  @project = project
26
13
  @gitlab = (dry_run ? GitlabIssueDryClient : GitlabIssueClient).new(token: token, project: project)
@@ -51,22 +38,16 @@ module GitlabQuality
51
38
 
52
39
  | Field | Value |
53
40
  | ------ | ------ |
54
- | File | #{test_file_link(test)} |
41
+ | File | #{test.test_file_link} |
55
42
  | Description | `#{test.name}` |
56
43
  | Test level | #{test.level} |
57
44
  | Hash | `#{test_hash(test)}` |
58
45
  | Duration | #{test.run_time} seconds |
59
- | Expected duration | < #{max_duration_for_test(test)} seconds |
46
+ | Expected duration | < #{test.max_duration_for_test} seconds |
60
47
  #{"| Test case | #{test.testcase} |" if test.testcase}
61
48
  DESCRIPTION
62
49
  end
63
50
 
64
- def test_file_link(test)
65
- path_prefix = test.file.start_with?('qa/') ? 'qa/' : ''
66
-
67
- "[`#{path_prefix}#{test.file}#L#{test.line_number}`](#{FILE_BASE_URL}#{path_prefix}#{test.file}#L#{test.line_number})"
68
- end
69
-
70
51
  def new_issue_labels(_test)
71
52
  []
72
53
  end
@@ -169,15 +150,6 @@ module GitlabQuality
169
150
  def ee_test?(test)
170
151
  test.file =~ %r{features/ee/(api|browser_ui)}
171
152
  end
172
-
173
- def max_duration_for_test(test)
174
- test_level_specification = TEST_LEVEL_SPECIFICATIONS.find do |test_level_specification|
175
- test.example_id =~ test_level_specification.regex
176
- end
177
- return OTHER_TESTS_MAX_DURATION unless test_level_specification
178
-
179
- test_level_specification.max_duration
180
- end
181
153
  end
182
154
  end
183
155
  end
@@ -27,7 +27,7 @@ module GitlabQuality
27
27
  puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
28
28
 
29
29
  test_results.each do |test|
30
- create_slow_issue(test) if should_create_slow_issue?(test)
30
+ create_slow_issue(test) if test.slow_test?
31
31
  end
32
32
  end
33
33
  end
@@ -59,10 +59,6 @@ module GitlabQuality
59
59
  rescue MultipleIssuesFound => e
60
60
  warn(e.message)
61
61
  end
62
-
63
- def should_create_slow_issue?(test)
64
- !test.allowed_to_be_slow? && test.run_time > max_duration_for_test(test)
65
- end
66
62
  end
67
63
  end
68
64
  end
@@ -4,8 +4,21 @@ module GitlabQuality
4
4
  module TestTooling
5
5
  module TestResult
6
6
  class JsonTestResult < BaseTestResult
7
+ FILE_BASE_URL = "https://gitlab.com/gitlab-org/gitlab/-/blob/master/"
8
+
7
9
  PRIVATE_TOKEN_REGEX = /(private_token=)[\w-]+/
8
10
 
11
+ OTHER_TESTS_MAX_DURATION = 45.40 # seconds
12
+
13
+ TestLevelSpecification = Struct.new(:regex, :max_duration)
14
+
15
+ TEST_LEVEL_SPECIFICATIONS = [
16
+ TestLevelSpecification.new(%r{spec/features/}, 50.13),
17
+ TestLevelSpecification.new(%r{spec/(controllers|requests)/}, 19.20),
18
+ TestLevelSpecification.new(%r{spec/lib/}, 27.12),
19
+ TestLevelSpecification.new(%r{qa/specs/features/}, 240)
20
+ ].freeze
21
+
9
22
  def name
10
23
  report.fetch('full_description')
11
24
  end
@@ -93,6 +106,10 @@ module GitlabQuality
93
106
  report['level']
94
107
  end
95
108
 
109
+ def ci_job_id
110
+ report['ci_job_url'].split('/').last
111
+ end
112
+
96
113
  def failures # rubocop:disable Metrics/AbcSize
97
114
  @failures ||=
98
115
  report.fetch('exceptions', []).filter_map do |exception|
@@ -123,6 +140,25 @@ module GitlabQuality
123
140
  !!report['allowed_to_be_slow']
124
141
  end
125
142
 
143
+ def slow_test?
144
+ !allowed_to_be_slow? && run_time > max_duration_for_test
145
+ end
146
+
147
+ def max_duration_for_test
148
+ test_level_specification = TEST_LEVEL_SPECIFICATIONS.find do |test_level_specification|
149
+ example_id =~ test_level_specification.regex
150
+ end
151
+ return OTHER_TESTS_MAX_DURATION unless test_level_specification
152
+
153
+ test_level_specification.max_duration
154
+ end
155
+
156
+ def test_file_link
157
+ path_prefix = file.start_with?('qa/') ? 'qa/' : ''
158
+
159
+ "[`#{path_prefix}#{file}#L#{line_number}`](#{FILE_BASE_URL}#{path_prefix}#{file}#L#{line_number})"
160
+ end
161
+
126
162
  private
127
163
 
128
164
  def quarantine
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "0.9.3"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  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.9.3
4
+ version: 1.0.0
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-07-26 00:00:00.000000000 Z
11
+ date: 2023-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -324,6 +324,7 @@ executables:
324
324
  - relate-failure-issue
325
325
  - report-results
326
326
  - slow-test-issues
327
+ - slow-test-merge-request-report-note
327
328
  - update-screenshot-paths
328
329
  extensions: []
329
330
  extra_rdoc_files: []
@@ -348,9 +349,12 @@ files:
348
349
  - exe/relate-failure-issue
349
350
  - exe/report-results
350
351
  - exe/slow-test-issues
352
+ - exe/slow-test-merge-request-report-note
351
353
  - exe/update-screenshot-paths
352
354
  - lefthook.yml
353
355
  - lib/gitlab_quality/test_tooling.rb
356
+ - lib/gitlab_quality/test_tooling/gitlab_client/merge_request.rb
357
+ - lib/gitlab_quality/test_tooling/gitlab_client/merge_request_dry_client.rb
354
358
  - lib/gitlab_quality/test_tooling/gitlab_issue_client.rb
355
359
  - lib/gitlab_quality/test_tooling/gitlab_issue_dry_client.rb
356
360
  - lib/gitlab_quality/test_tooling/labels_inference.rb
@@ -359,6 +363,7 @@ files:
359
363
  - lib/gitlab_quality/test_tooling/report/concerns/results_reporter.rb
360
364
  - lib/gitlab_quality/test_tooling/report/concerns/utils.rb
361
365
  - lib/gitlab_quality/test_tooling/report/generate_test_session.rb
366
+ - lib/gitlab_quality/test_tooling/report/merge_request_slow_tests_report.rb
362
367
  - lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb
363
368
  - lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb
364
369
  - lib/gitlab_quality/test_tooling/report/report_as_issue.rb