gitlab_quality-test_tooling 3.9.0 → 3.10.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/.rubocop_todo.yml +0 -6
- data/AGENTS.md +95 -0
- data/Gemfile.lock +1 -1
- data/README.md +0 -15
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/test_metrics.rb +11 -2
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +3 -7
- data/exe/knapsack-report-issues +0 -54
- data/lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time.rb +0 -120
- data/lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time_report.rb +0 -66
- data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +0 -150
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c49f987999c6124651b5f1f21de7d588d1940749b91f8527e21b9fcbbb1921b
|
|
4
|
+
data.tar.gz: 384d0765fb0d891e373777004ad20a5cecbc2df9b07cc74ca1b70ae30a080641
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ff2cc394058616512c5c00779772d244d08986e12762a49c17ca9e31065a4af6b79beaeb8ceb7ac35c290efb10410c5888aff363b183d4190bb9b863278a418
|
|
7
|
+
data.tar.gz: 8cc8717e64a9c260ca10474897aae319bae9565a1257815e3a83e931049d8d0c61c32fb38d9b66f13976f0f0e1ec4e7d3666f5c3311c5e64cae0c25940ff5da6
|
data/.rubocop_todo.yml
CHANGED
|
@@ -59,9 +59,3 @@ Style/RedundantReturn:
|
|
|
59
59
|
Exclude:
|
|
60
60
|
- 'lib/gitlab_quality/test_tooling/runtime/env.rb'
|
|
61
61
|
|
|
62
|
-
# Offense count: 1
|
|
63
|
-
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
64
|
-
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
65
|
-
Style/ReturnNilInPredicateMethodDefinition:
|
|
66
|
-
Exclude:
|
|
67
|
-
- 'lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time.rb'
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# gitlab_quality-test_tooling
|
|
2
|
+
|
|
3
|
+
Ruby gem providing test tooling for GitLab CI — metrics export, test quarantine, failure reporting, flaky test tracking, and code coverage analysis.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- Ruby (see `.ruby-version` and gemspec for version), gem published to RubyGems
|
|
8
|
+
- RSpec for testing, RuboCop (`gitlab-styles` base) for linting
|
|
9
|
+
- Lefthook for pre-push hooks (rspec, rubocop, changelog trailer check)
|
|
10
|
+
|
|
11
|
+
## Testing
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle exec rspec
|
|
15
|
+
bundle exec rubocop
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commit Conventions
|
|
19
|
+
|
|
20
|
+
Every branch must have a `Changelog:` git trailer on its **first commit** (relative to `origin/main`).
|
|
21
|
+
|
|
22
|
+
Valid values: `added`, `fixed`, `changed`, `deprecated`, `removed`, `security`, `performance`, `other`
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Fix failure_exception for MultipleExceptionError
|
|
28
|
+
|
|
29
|
+
Description of the change.
|
|
30
|
+
|
|
31
|
+
Changelog: fixed
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Enforcement
|
|
35
|
+
|
|
36
|
+
- **lefthook pre-push hook** checks the first commit on the branch
|
|
37
|
+
- **gitlab-dangerfiles** checks in CI (Danger bot comments on MRs)
|
|
38
|
+
- `--no-verify` bypasses lefthook locally, but CI will still fail without the trailer
|
|
39
|
+
|
|
40
|
+
### Choosing the right value
|
|
41
|
+
|
|
42
|
+
| Value | When to use |
|
|
43
|
+
|-------|-------------|
|
|
44
|
+
| `added` | New feature or capability |
|
|
45
|
+
| `fixed` | Bug fix |
|
|
46
|
+
| `changed` | Non-breaking change to existing behavior |
|
|
47
|
+
| `deprecated` | Feature marked for removal |
|
|
48
|
+
| `removed` | Feature removed |
|
|
49
|
+
| `security` | Security fix |
|
|
50
|
+
| `performance` | Performance improvement |
|
|
51
|
+
| `other` | Refactoring, docs, CI changes |
|
|
52
|
+
|
|
53
|
+
Reference: https://docs.gitlab.com/ee/development/changelog.html
|
|
54
|
+
|
|
55
|
+
### Commit Message Format
|
|
56
|
+
|
|
57
|
+
- Present tense ("Fix bug" not "Fixed bug")
|
|
58
|
+
- First line under 72 characters
|
|
59
|
+
- Blank line between subject and body
|
|
60
|
+
- `Changelog:` trailer at the end of the first commit's message, after a blank line
|
|
61
|
+
|
|
62
|
+
## Project Structure
|
|
63
|
+
|
|
64
|
+
| Directory | Purpose |
|
|
65
|
+
|-----------|---------|
|
|
66
|
+
| `lib/gitlab_quality/test_tooling/` | Main gem code |
|
|
67
|
+
| `exe/` | CLI executables |
|
|
68
|
+
| `spec/` | RSpec tests |
|
|
69
|
+
| `.gitlab/changelog_config.yml` | Changelog category mapping |
|
|
70
|
+
|
|
71
|
+
## Key Modules
|
|
72
|
+
|
|
73
|
+
- **TestMetricsExporter** — Serializes RSpec results to ClickHouse (exception classes, failure messages, CI metadata)
|
|
74
|
+
- **Report** — Creates/updates GitLab issues for test failures, flaky tests, slow tests
|
|
75
|
+
- **TestQuarantine** — RSpec formatter that skips quarantined tests
|
|
76
|
+
- **CodeCoverage** — Coverage analysis and responsibility patterns
|
|
77
|
+
- **ClickHouse::Client** — Batch HTTP client for pushing metrics
|
|
78
|
+
|
|
79
|
+
## Release Process
|
|
80
|
+
|
|
81
|
+
The [automated gem release process](https://gitlab.com/gitlab-org/quality/pipeline-common#release-process) publishes new versions via CI. It will:
|
|
82
|
+
|
|
83
|
+
- Publish the gem to https://rubygems.org/gems/gitlab_quality-test_tooling
|
|
84
|
+
- Create a release at https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling/-/releases
|
|
85
|
+
- Populate release notes from changelog entries
|
|
86
|
+
|
|
87
|
+
### Steps to release
|
|
88
|
+
|
|
89
|
+
1. Create a branch (e.g., `bump-version-X-Y-Z`)
|
|
90
|
+
2. Update `lib/gitlab_quality/test_tooling/version.rb` with the new version
|
|
91
|
+
3. Run `bundle install` to update `Gemfile.lock`
|
|
92
|
+
4. Commit with message like `Update gem version to X.Y.Z` and trailer `Changelog: changed`
|
|
93
|
+
5. Create an MR using the **Release** MR template (`.gitlab/merge_request_templates/Release.md`)
|
|
94
|
+
6. Fill in the template: update the diff link, check release notes, verify SemVer
|
|
95
|
+
7. Get the MR merged by a maintainer — CI will publish the gem automatically
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -141,21 +141,6 @@ Usage: exe/slow-test-issues [options]
|
|
|
141
141
|
-h, --help Show the usage
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
### `exe/knapsack-report-issues`
|
|
145
|
-
|
|
146
|
-
```shell
|
|
147
|
-
Purpose: Create spec run time issue when a spec file almost caused job timeout because it ran significantly longer than what Knapsack expected.
|
|
148
|
-
Usage: exe/knapsack-report-issues [options]
|
|
149
|
-
-i, --input-file INPUT_FILE Knapsack actual run time report file path glob
|
|
150
|
-
-e EXPECTED_REPORT, Knapsack expected report file path
|
|
151
|
-
--expected-report
|
|
152
|
-
-p, --project PROJECT Can be an integer or a group/project string
|
|
153
|
-
-t, --token TOKEN A valid access token with `api` scope and Maintainer permission in PROJECT
|
|
154
|
-
--dry-run Perform a dry-run (don't create issues)
|
|
155
|
-
-v, --version Show the version
|
|
156
|
-
-h, --help Show the usage
|
|
157
|
-
```
|
|
158
|
-
|
|
159
144
|
### `exe/failed-test-issues`
|
|
160
145
|
|
|
161
146
|
```shell
|
|
@@ -161,14 +161,23 @@ module GitlabQuality
|
|
|
161
161
|
exception.all_exceptions.flatten
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
# Truncated exception
|
|
164
|
+
# Truncated exception message
|
|
165
|
+
#
|
|
166
|
+
# For MultipleExceptionError, returns the first wrapped exception's message
|
|
167
|
+
# instead of the unhelpful wrapper class name.
|
|
165
168
|
#
|
|
166
169
|
# @return [String]
|
|
167
170
|
def failure_exception
|
|
168
171
|
exception = example.execution_result.exception
|
|
169
172
|
return unless exception
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
source = if exception.respond_to?(:all_exceptions)
|
|
175
|
+
exception.all_exceptions.flatten.first || exception
|
|
176
|
+
else
|
|
177
|
+
exception
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
source.to_s.tr("\n", " ").slice(0, 1000)
|
|
172
181
|
end
|
|
173
182
|
|
|
174
183
|
# Test run type | suite name
|
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: 3.
|
|
4
|
+
version: 3.10.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: 2026-03-
|
|
11
|
+
date: 2026-03-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: climate_control
|
|
@@ -420,7 +420,6 @@ executables:
|
|
|
420
420
|
- feature-readiness-checklist
|
|
421
421
|
- feature-readiness-evaluation
|
|
422
422
|
- flaky-test-issues
|
|
423
|
-
- knapsack-report-issues
|
|
424
423
|
- post-to-slack
|
|
425
424
|
- prepare-stage-reports
|
|
426
425
|
- relate-failure-issue
|
|
@@ -439,6 +438,7 @@ files:
|
|
|
439
438
|
- ".rubocop_todo.yml"
|
|
440
439
|
- ".ruby-version"
|
|
441
440
|
- ".tool-versions"
|
|
441
|
+
- AGENTS.md
|
|
442
442
|
- CODE_OF_CONDUCT.md
|
|
443
443
|
- CONTRIBUTING.md
|
|
444
444
|
- Dangerfile
|
|
@@ -454,7 +454,6 @@ files:
|
|
|
454
454
|
- exe/feature-readiness-checklist
|
|
455
455
|
- exe/feature-readiness-evaluation
|
|
456
456
|
- exe/flaky-test-issues
|
|
457
|
-
- exe/knapsack-report-issues
|
|
458
457
|
- exe/post-to-slack
|
|
459
458
|
- exe/prepare-stage-reports
|
|
460
459
|
- exe/relate-failure-issue
|
|
@@ -514,8 +513,6 @@ files:
|
|
|
514
513
|
- lib/gitlab_quality/test_tooling/gitlab_client/work_items_client.rb
|
|
515
514
|
- lib/gitlab_quality/test_tooling/gitlab_client/work_items_dry_client.rb
|
|
516
515
|
- lib/gitlab_quality/test_tooling/job_trace_analyzer.rb
|
|
517
|
-
- lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time.rb
|
|
518
|
-
- lib/gitlab_quality/test_tooling/knapsack_reports/spec_run_time_report.rb
|
|
519
516
|
- lib/gitlab_quality/test_tooling/labels_inference.rb
|
|
520
517
|
- lib/gitlab_quality/test_tooling/report/concerns/group_and_category_labels.rb
|
|
521
518
|
- lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb
|
|
@@ -537,7 +534,6 @@ files:
|
|
|
537
534
|
- lib/gitlab_quality/test_tooling/report/group_issues/issue_updater.rb
|
|
538
535
|
- lib/gitlab_quality/test_tooling/report/health_problem_reporter.rb
|
|
539
536
|
- lib/gitlab_quality/test_tooling/report/issue_logger.rb
|
|
540
|
-
- lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb
|
|
541
537
|
- lib/gitlab_quality/test_tooling/report/merge_request_slow_tests_report.rb
|
|
542
538
|
- lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb
|
|
543
539
|
- lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb
|
data/exe/knapsack-report-issues
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
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-file INPUT_FILE", String, "Knapsack actual run time report file path glob") do |input_file|
|
|
15
|
-
params[:input_files] = input_file
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
opts.on("-e", "--expected-report EXPECTED_REPORT", String, "Knapsack expected report file path") do |report_path|
|
|
19
|
-
params[:expected_report] = report_path.strip
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
|
|
23
|
-
params[:project] = project
|
|
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 issues)") 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 spec run time issue when a spec file almost caused job timeout because it ran significantly longer than what Knapsack expected."
|
|
42
|
-
puts opts
|
|
43
|
-
exit
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
opts.parse(ARGV)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
if params.any?
|
|
50
|
-
GitlabQuality::TestTooling::Report::KnapsackReportIssue.new(**params).invoke!
|
|
51
|
-
else
|
|
52
|
-
puts options
|
|
53
|
-
exit 1
|
|
54
|
-
end
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module GitlabQuality
|
|
4
|
-
module TestTooling
|
|
5
|
-
module KnapsackReports
|
|
6
|
-
class SpecRunTime
|
|
7
|
-
attr_reader :file, :expected, :actual, :expected_suite_duration, :actual_suite_duration, :project, :ref
|
|
8
|
-
|
|
9
|
-
ACTUAL_TO_EXPECTED_SPEC_RUN_TIME_RATIO_THRESHOLD = 1.5 # actual run time is longer than expected by 50% +
|
|
10
|
-
SPEC_WEIGHT_PERCENTAGE_TRESHOLD = 15 # a spec file takes 15%+ of the total test suite run time
|
|
11
|
-
SUITE_DURATION_THRESHOLD = 70 * 60 # if test suite takes more than 70 minutes, job risks timing out
|
|
12
|
-
FEATURE_CATEGORY_METADATA_REGEX = /(?<=feature_category: :)(?<feature_category>\w+)/
|
|
13
|
-
|
|
14
|
-
def initialize(
|
|
15
|
-
file:,
|
|
16
|
-
expected:,
|
|
17
|
-
actual:,
|
|
18
|
-
expected_suite_duration:,
|
|
19
|
-
actual_suite_duration:,
|
|
20
|
-
token: '',
|
|
21
|
-
project: Runtime::Env.ci_project_path,
|
|
22
|
-
ref: Runtime::Env.ci_commit_ref_name)
|
|
23
|
-
@file = file
|
|
24
|
-
@expected = expected.to_f
|
|
25
|
-
@actual = actual.to_f
|
|
26
|
-
@expected_suite_duration = expected_suite_duration.to_f
|
|
27
|
-
@actual_suite_duration = actual_suite_duration.to_f
|
|
28
|
-
@token = token
|
|
29
|
-
@project = project
|
|
30
|
-
@ref = ref
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def feature_category
|
|
34
|
-
file_lines.each do |line|
|
|
35
|
-
match = FEATURE_CATEGORY_METADATA_REGEX.match(line)
|
|
36
|
-
next unless match
|
|
37
|
-
|
|
38
|
-
break match[:feature_category]
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def should_report?
|
|
43
|
-
# guideline proposed in https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/354
|
|
44
|
-
exceed_actual_to_expected_ratio_threshold? && test_suite_bottleneck?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def ci_pipeline_url_markdown
|
|
48
|
-
"[#{ci_pipeline_id}](#{ci_pipeline_url})"
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def ci_pipeline_created_at
|
|
52
|
-
ENV.fetch('CI_PIPELINE_CREATED_AT', nil)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def ci_job_link_markdown
|
|
56
|
-
"[#{ci_job_name}](#{ci_job_url})"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def file_link_markdown
|
|
60
|
-
"[#{file}](#{file_link})"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def actual_percentage
|
|
64
|
-
(actual / actual_suite_duration * 100).round(2)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def name
|
|
68
|
-
nil
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
attr_reader :token
|
|
74
|
-
|
|
75
|
-
def exceed_actual_to_expected_ratio_threshold?
|
|
76
|
-
actual / expected >= ACTUAL_TO_EXPECTED_SPEC_RUN_TIME_RATIO_THRESHOLD
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def test_suite_bottleneck?
|
|
80
|
-
# now we only report bottlenecks when they risk causing job timeouts
|
|
81
|
-
return unless actual_suite_duration > SUITE_DURATION_THRESHOLD
|
|
82
|
-
|
|
83
|
-
actual_percentage > SPEC_WEIGHT_PERCENTAGE_TRESHOLD
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def ci_job_url
|
|
87
|
-
ENV.fetch('CI_JOB_URL', nil)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def ci_job_name
|
|
91
|
-
ENV.fetch('CI_JOB_NAME_SLUG', nil)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def ci_pipeline_id
|
|
95
|
-
ENV.fetch('CI_PIPELINE_IID', nil)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def ci_pipeline_url
|
|
99
|
-
ENV.fetch('CI_PIPELINE_URL', nil)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def file_link
|
|
103
|
-
"https://gitlab.com/#{project}/-/blob/#{Runtime::Env.ci_commit_ref_name}/#{file}"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def file_lines
|
|
107
|
-
files_client.file_contents.lines(chomp: true)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def files_client
|
|
111
|
-
@files_client ||= GitlabClient::RepositoryFilesClient.new(
|
|
112
|
-
token: token,
|
|
113
|
-
project: project,
|
|
114
|
-
file_path: file,
|
|
115
|
-
ref: ref)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
|
-
module GitlabQuality
|
|
6
|
-
module TestTooling
|
|
7
|
-
module KnapsackReports
|
|
8
|
-
class SpecRunTimeReport
|
|
9
|
-
attr_reader :project, :expected_report, :actual_report
|
|
10
|
-
|
|
11
|
-
def initialize(project:, expected_report_path:, actual_report_path:, token: '')
|
|
12
|
-
@project = project
|
|
13
|
-
@expected_report = parse(expected_report_path)
|
|
14
|
-
@actual_report = parse(actual_report_path)
|
|
15
|
-
@token = token
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def filtered_report
|
|
19
|
-
@filtered_report = actual_report.keys.filter_map do |spec_file|
|
|
20
|
-
expected_run_time = expected_report[spec_file]
|
|
21
|
-
actual_run_time = actual_report[spec_file]
|
|
22
|
-
|
|
23
|
-
if expected_run_time.nil?
|
|
24
|
-
puts "#{spec_file} missing from the expected Knapsack report, skipping."
|
|
25
|
-
next
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
spec_run_time = SpecRunTime.new(
|
|
29
|
-
token: token,
|
|
30
|
-
project: project,
|
|
31
|
-
file: spec_file,
|
|
32
|
-
expected: expected_run_time,
|
|
33
|
-
actual: actual_run_time,
|
|
34
|
-
expected_suite_duration: expected_test_suite_run_time_total,
|
|
35
|
-
actual_suite_duration: actual_test_suite_run_time_total
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
spec_run_time if spec_run_time.should_report?
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
attr_reader :token
|
|
45
|
-
|
|
46
|
-
def parse(report_path)
|
|
47
|
-
JSON.parse(File.read(report_path))
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def expected_test_suite_run_time_total
|
|
51
|
-
@expected_test_suite_run_time_total ||=
|
|
52
|
-
expected_report.reduce(0) do |total_run_time, (_spec_file, run_time)|
|
|
53
|
-
total_run_time + run_time
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def actual_test_suite_run_time_total
|
|
58
|
-
@actual_test_suite_run_time_total ||=
|
|
59
|
-
actual_report.reduce(0) do |total_run_time, (_spec_file, run_time)|
|
|
60
|
-
total_run_time + run_time
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module GitlabQuality
|
|
4
|
-
module TestTooling
|
|
5
|
-
module Report
|
|
6
|
-
# Uses the API to create GitLab issues for spec run time exceeding Knapsack expectation
|
|
7
|
-
#
|
|
8
|
-
# - Takes the expected and actual Knapsack JSON reports from the knapsack output
|
|
9
|
-
# - Takes a project where issues should be created
|
|
10
|
-
# - For every test file reported with unexpectedly long run time:
|
|
11
|
-
# - Find issue by test file name, and if found:
|
|
12
|
-
# - Reopen issue if it already exists, but is closed
|
|
13
|
-
# - Update the issue with the new run time data
|
|
14
|
-
# - If not found:
|
|
15
|
-
# - Create a new issue with the run time data
|
|
16
|
-
class KnapsackReportIssue < ReportAsIssue
|
|
17
|
-
include Concerns::GroupAndCategoryLabels
|
|
18
|
-
|
|
19
|
-
NEW_ISSUE_LABELS = Set.new([
|
|
20
|
-
'test', 'automation:bot-authored', 'type::maintenance', 'maintenance::performance',
|
|
21
|
-
'priority::3', 'severity::3', 'knapsack_report', 'suppress-contributor-links'
|
|
22
|
-
]).freeze
|
|
23
|
-
SEARCH_LABELS = %w[test maintenance::performance knapsack_report].freeze
|
|
24
|
-
JOB_TIMEOUT_EPIC_URL = 'https://gitlab.com/groups/gitlab-org/quality/engineering-productivity/-/epics/19'
|
|
25
|
-
|
|
26
|
-
def initialize(token:, input_files:, expected_report:, project: nil, dry_run: false)
|
|
27
|
-
super
|
|
28
|
-
|
|
29
|
-
@expected_report = expected_report
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
attr_reader :expected_report
|
|
35
|
-
|
|
36
|
-
def run!
|
|
37
|
-
puts "Reporting spec file exceeding Knapsack expectaton issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
|
38
|
-
|
|
39
|
-
search_and_create_issue
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def new_issue_labels(test)
|
|
43
|
-
up_to_date_labels(test: test, new_labels: NEW_ISSUE_LABELS + group_and_category_labels_for_test(test))
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def search_and_create_issue
|
|
47
|
-
filtered_report = KnapsackReports::SpecRunTimeReport.new(
|
|
48
|
-
token: token,
|
|
49
|
-
project: project,
|
|
50
|
-
expected_report_path: expected_report_path,
|
|
51
|
-
actual_report_path: actual_report_path
|
|
52
|
-
).filtered_report
|
|
53
|
-
|
|
54
|
-
puts "=> Reporting #{filtered_report.count} spec files exceeding Knapsack expectation."
|
|
55
|
-
|
|
56
|
-
filtered_report.each do |spec_with_run_time|
|
|
57
|
-
existing_issues = find_issues_for_test(spec_with_run_time, labels: SEARCH_LABELS)
|
|
58
|
-
|
|
59
|
-
if existing_issues.empty?
|
|
60
|
-
puts "Creating issue for #{spec_with_run_time.file}"
|
|
61
|
-
create_issue(spec_with_run_time)
|
|
62
|
-
else
|
|
63
|
-
update_issue(issue: existing_issues.last, spec_run_time: spec_with_run_time)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def expected_report_path
|
|
69
|
-
return if expected_report.nil? || !File.exist?(expected_report)
|
|
70
|
-
|
|
71
|
-
expected_report
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def actual_report_path
|
|
75
|
-
return if files.nil? || !File.exist?(files.first)
|
|
76
|
-
|
|
77
|
-
files.first
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def new_issue_title(spec_run_time)
|
|
81
|
-
"Job timeout risk: #{spec_run_time.file} ran much longer than expected"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def new_issue_description(spec_run_time)
|
|
85
|
-
<<~MARKDOWN.chomp
|
|
86
|
-
/epic #{JOB_TIMEOUT_EPIC_URL}
|
|
87
|
-
|
|
88
|
-
### Why was this issue created?
|
|
89
|
-
|
|
90
|
-
#{spec_run_time.file_link_markdown} was reported to have:
|
|
91
|
-
|
|
92
|
-
1. exceeded Knapsack's expected runtime by at least 50%, and
|
|
93
|
-
2. been identified as a notable pipeline bottleneck and a job timeout risk
|
|
94
|
-
|
|
95
|
-
### Suggested steps for investigation
|
|
96
|
-
|
|
97
|
-
1. To reproduce in CI by running test files in the same order, you can follow the steps listed [here](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html#recreate-job-failure-in-ci-by-forcing-the-job-to-run-the-same-set-of-test-files).
|
|
98
|
-
1. Identify if a specific test case is stalling the run time. Hint: You can search the job's log for `Starting example group #{spec_run_time.file}` and view the elapsed time after each test case in the proceeding lines starting with `[RSpecRunTime]`.
|
|
99
|
-
1. If the test file is large, consider refactoring it into multiple files to allow better test parallelization across runners.
|
|
100
|
-
1. If the run time cannot be fixed in time, consider quarantine the spec(s) to restore performance.
|
|
101
|
-
|
|
102
|
-
### Run time details
|
|
103
|
-
|
|
104
|
-
#{run_time_detail(spec_run_time)}
|
|
105
|
-
MARKDOWN
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def update_issue(issue:, spec_run_time:)
|
|
109
|
-
updated_description = <<~MARKDOWN.chomp
|
|
110
|
-
#{issue.description}
|
|
111
|
-
|
|
112
|
-
#{run_time_detail(spec_run_time)}
|
|
113
|
-
MARKDOWN
|
|
114
|
-
|
|
115
|
-
issue_attrs = {
|
|
116
|
-
description: updated_description
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
# We reopen closed issues to not lose any history
|
|
120
|
-
state_event = issue.state == 'closed' ? 'reopen' : nil
|
|
121
|
-
issue_attrs[:state_event] = state_event if state_event
|
|
122
|
-
|
|
123
|
-
gitlab.edit_issue(iid: issue.iid, options: issue_attrs)
|
|
124
|
-
puts " => Added a report in #{issue.web_url}!"
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def run_time_detail(spec_run_time)
|
|
128
|
-
<<~MARKDOWN.chomp
|
|
129
|
-
- Reported from pipeline #{spec_run_time.ci_pipeline_url_markdown} created at `#{spec_run_time.ci_pipeline_created_at}`
|
|
130
|
-
|
|
131
|
-
| Field | Value |
|
|
132
|
-
| ------ | ------ |
|
|
133
|
-
| Job URL| #{spec_run_time.ci_job_link_markdown} |
|
|
134
|
-
| Job total RSpec suite run time | expected: `#{readable_duration(spec_run_time.expected_suite_duration)}`, actual: `#{readable_duration(spec_run_time.actual_suite_duration)}` |
|
|
135
|
-
| Spec file run time | expected: `#{readable_duration(spec_run_time.expected)}`, actual: `#{readable_duration(spec_run_time.actual)}` |
|
|
136
|
-
| Spec file weight | `#{spec_run_time.actual_percentage}%` of total suite run time |
|
|
137
|
-
MARKDOWN
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def assert_input_files!(_files)
|
|
141
|
-
missing_expected_report_msg = "Missing a valid expected Knapsack report."
|
|
142
|
-
missing_actual_report_msg = "Missing a valid actual Knapsack report."
|
|
143
|
-
|
|
144
|
-
abort missing_expected_report_msg if expected_report_path.nil?
|
|
145
|
-
abort missing_actual_report_msg if actual_report_path.nil?
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|