gitlab-qa 10.3.0 → 12.2.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/.gitignore +1 -1
- data/.gitlab/changelog_config.yml +13 -0
- data/.gitlab/merge_request_templates/Release.md +13 -34
- data/.gitlab-ci.yml +3 -1
- data/.rubocop.yml +13 -2
- data/.rubocop_todo.yml +57 -97
- data/Dangerfile +1 -5
- data/Gemfile.lock +47 -39
- data/README.md +1 -2
- data/docs/release_process.md +1 -1
- data/docs/running_against_remote_grid.md +42 -3
- data/docs/what_tests_can_be_run.md +5 -2
- data/gitlab-qa.gemspec +5 -3
- data/lib/gitlab/qa/component/base.rb +9 -9
- data/lib/gitlab/qa/component/gitaly.rb +7 -5
- data/lib/gitlab/qa/component/gitaly_cluster.rb +15 -9
- data/lib/gitlab/qa/component/gitlab.rb +48 -21
- data/lib/gitlab/qa/component/mail_hog.rb +1 -0
- data/lib/gitlab/qa/component/praefect.rb +41 -31
- data/lib/gitlab/qa/component/selenoid.rb +14 -7
- data/lib/gitlab/qa/component/specs.rb +11 -5
- data/lib/gitlab/qa/component/staging.rb +4 -4
- data/lib/gitlab/qa/component/telegraf.rb +2 -1
- data/lib/gitlab/qa/docker/engine.rb +6 -3
- data/lib/gitlab/qa/docker/volumes.rb +1 -1
- data/lib/gitlab/qa/release.rb +4 -4
- data/lib/gitlab/qa/runner.rb +10 -3
- data/lib/gitlab/qa/runtime/env.rb +47 -62
- data/lib/gitlab/qa/runtime/logger.rb +1 -1
- data/lib/gitlab/qa/runtime/omnibus_configuration.rb +1 -0
- data/lib/gitlab/qa/runtime/omnibus_configurations/decomposition_single_db.rb +1 -2
- data/lib/gitlab/qa/runtime/omnibus_configurations/object_storage_gcs.rb +2 -1
- data/lib/gitlab/qa/runtime/scenario.rb +1 -5
- data/lib/gitlab/qa/scenario/actable.rb +4 -4
- data/lib/gitlab/qa/scenario/test/instance/airgapped.rb +2 -4
- data/lib/gitlab/qa/scenario/test/instance/deployment_base.rb +2 -1
- data/lib/gitlab/qa/scenario/test/integration/gitaly_cluster.rb +0 -2
- data/lib/gitlab/qa/scenario/test/integration/group_saml.rb +1 -1
- data/lib/gitlab/qa/scenario/test/integration/ldap.rb +5 -6
- data/lib/gitlab/qa/scenario/test/integration/mtls.rb +20 -6
- data/lib/gitlab/qa/scenario/test/integration/oauth.rb +13 -4
- data/lib/gitlab/qa/scenario/test/integration/praefect.rb +16 -10
- data/lib/gitlab/qa/scenario/test/integration/registry_with_cdn.rb +5 -2
- data/lib/gitlab/qa/scenario/test/omnibus/update_from_previous.rb +1 -1
- data/lib/gitlab/qa/scenario/test/omnibus/upgrade.rb +1 -3
- data/lib/gitlab/qa/scenario/test/sanity/version.rb +1 -1
- data/lib/gitlab/qa/support/config_scripts.rb +1 -1
- data/lib/gitlab/qa/support/gitlab_version_info.rb +30 -10
- data/lib/gitlab/qa/support/shell_command.rb +1 -0
- data/lib/gitlab/qa/test_logger.rb +2 -2
- data/lib/gitlab/qa/version.rb +1 -1
- data/lib/gitlab/qa.rb +0 -1
- data/support/data/admin_access_token_seed.rb +5 -1
- data/support/data/license_usage_seed.rb +3 -1
- metadata +16 -45
- data/bin/slack +0 -14
- data/exe/gitlab-qa-report +0 -10
- data/lib/gitlab/qa/report/base_test_results.rb +0 -39
- data/lib/gitlab/qa/report/find_set_dri.rb +0 -43
- data/lib/gitlab/qa/report/generate_test_session.rb +0 -275
- data/lib/gitlab/qa/report/gitlab_issue_client.rb +0 -190
- data/lib/gitlab/qa/report/gitlab_issue_dry_client.rb +0 -28
- data/lib/gitlab/qa/report/j_unit_test_results.rb +0 -27
- data/lib/gitlab/qa/report/json_test_results.rb +0 -29
- data/lib/gitlab/qa/report/prepare_stage_reports.rb +0 -86
- data/lib/gitlab/qa/report/relate_failure_issue.rb +0 -374
- data/lib/gitlab/qa/report/report_as_issue.rb +0 -176
- data/lib/gitlab/qa/report/report_results.rb +0 -64
- data/lib/gitlab/qa/report/results_in_issues.rb +0 -126
- data/lib/gitlab/qa/report/results_in_testcases.rb +0 -111
- data/lib/gitlab/qa/report/results_reporter_shared.rb +0 -70
- data/lib/gitlab/qa/report/summary_table.rb +0 -43
- data/lib/gitlab/qa/report/test_result.rb +0 -184
- data/lib/gitlab/qa/report/update_screenshot_path.rb +0 -63
- data/lib/gitlab/qa/reporter.rb +0 -131
- data/lib/gitlab/qa/runtime/omnibus_configurations/packages.rb +0 -17
- data/lib/gitlab/qa/runtime/token_finder.rb +0 -44
- data/lib/gitlab/qa/slack/post_to_slack.rb +0 -30
- data/lib/gitlab/qa/system_logs/finders/json_log_finder.rb +0 -65
- data/lib/gitlab/qa/system_logs/finders/rails/api_log_finder.rb +0 -21
- data/lib/gitlab/qa/system_logs/finders/rails/application_log_finder.rb +0 -21
- data/lib/gitlab/qa/system_logs/finders/rails/exception_log_finder.rb +0 -21
- data/lib/gitlab/qa/system_logs/finders/rails/graphql_log_finder.rb +0 -21
- data/lib/gitlab/qa/system_logs/log_types/log.rb +0 -38
- data/lib/gitlab/qa/system_logs/log_types/rails/api_log.rb +0 -34
- data/lib/gitlab/qa/system_logs/log_types/rails/application_log.rb +0 -27
- data/lib/gitlab/qa/system_logs/log_types/rails/exception_log.rb +0 -23
- data/lib/gitlab/qa/system_logs/log_types/rails/graphql_log.rb +0 -30
- data/lib/gitlab/qa/system_logs/shared_fields.rb +0 -29
- data/lib/gitlab/qa/system_logs/system_logs_formatter.rb +0 -65
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Gitlab
|
|
4
|
-
module QA
|
|
5
|
-
module Report
|
|
6
|
-
# Uses the API to create or update GitLab test result issues with the results of tests from RSpec report files.
|
|
7
|
-
class ResultsInIssues < ReportAsIssue
|
|
8
|
-
include ResultsReporterShared
|
|
9
|
-
|
|
10
|
-
def initialize(**kwargs)
|
|
11
|
-
super
|
|
12
|
-
@issue_type = 'issue'
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def get_related_issue(testcase, test)
|
|
16
|
-
issue = find_linked_results_issue_by_iid(testcase, test)
|
|
17
|
-
is_new = false
|
|
18
|
-
|
|
19
|
-
if issue
|
|
20
|
-
issue = update_issue_title(issue, test) if issue_title_needs_updating?(issue, test)
|
|
21
|
-
else
|
|
22
|
-
puts "No valid issue link found"
|
|
23
|
-
issue = find_or_create_results_issue(test)
|
|
24
|
-
is_new = true
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
[issue, is_new]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def update_issue(issue, test)
|
|
31
|
-
new_labels = issue_labels(issue)
|
|
32
|
-
new_labels |= ['Testcase Linked']
|
|
33
|
-
|
|
34
|
-
labels_updated = update_labels(issue, test, new_labels)
|
|
35
|
-
note_posted = note_status(issue, test)
|
|
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."
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def find_linked_results_issue_by_iid(testcase, test)
|
|
47
|
-
iid = issue_iid_from_testcase(testcase)
|
|
48
|
-
|
|
49
|
-
return unless iid
|
|
50
|
-
|
|
51
|
-
find_issue_by_iid(iid)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def find_or_create_results_issue(test)
|
|
55
|
-
find_issue(test) || create_issue(test)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def issue_iid_from_testcase(testcase)
|
|
59
|
-
results = testcase.description.partition(TEST_CASE_RESULTS_SECTION_TEMPLATE).last if testcase.description.include?(TEST_CASE_RESULTS_SECTION_TEMPLATE)
|
|
60
|
-
|
|
61
|
-
return puts "No issue link found" unless results
|
|
62
|
-
|
|
63
|
-
issue_iid = results.split('/').last
|
|
64
|
-
|
|
65
|
-
issue_iid&.to_i
|
|
66
|
-
end
|
|
67
|
-
|
|
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
|
-
return gitlab.add_note_to_issue_discussion_as_thread(iid: issue.iid, discussion_id: discussion.id, body: failure_summary) if new_note_matches_discussion?(note, discussion)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
gitlab.create_issue_note(iid: issue.iid, note: note)
|
|
79
|
-
|
|
80
|
-
true
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def note_content(test)
|
|
84
|
-
errors = test.failures.each_with_object([]) do |failure, text|
|
|
85
|
-
text << <<~TEXT
|
|
86
|
-
Error:
|
|
87
|
-
```
|
|
88
|
-
#{failure['message']}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Stacktrace:
|
|
92
|
-
```
|
|
93
|
-
#{failure['stacktrace']}
|
|
94
|
-
```
|
|
95
|
-
TEXT
|
|
96
|
-
end.join("\n\n")
|
|
97
|
-
|
|
98
|
-
"#{failure_summary}\n\n#{errors}"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def failure_summary
|
|
102
|
-
summary = [":x: ~\"#{pipeline}::failed\""]
|
|
103
|
-
summary << "in job `#{Runtime::Env.ci_job_name}` in #{Runtime::Env.ci_job_url}"
|
|
104
|
-
summary.join(' ')
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def new_note_matches_discussion?(note, discussion)
|
|
108
|
-
note_error = error_and_stack_trace(note)
|
|
109
|
-
discussion_error = error_and_stack_trace(discussion.notes.first['body'])
|
|
110
|
-
|
|
111
|
-
return false if note_error.empty? || discussion_error.empty?
|
|
112
|
-
|
|
113
|
-
note_error == discussion_error
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def error_and_stack_trace(text)
|
|
117
|
-
text.strip[/Error:(.*)/m, 1].to_s
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def updated_description(issue, test)
|
|
121
|
-
new_issue_description(test)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'erb'
|
|
4
|
-
|
|
5
|
-
module Gitlab
|
|
6
|
-
module QA
|
|
7
|
-
module Report
|
|
8
|
-
# Uses the API to create or update GitLab test cases with the results of tests from RSpec report files.
|
|
9
|
-
class ResultsInTestCases < ReportAsIssue
|
|
10
|
-
attr_reader :issue_type, :gitlab
|
|
11
|
-
|
|
12
|
-
include ResultsReporterShared
|
|
13
|
-
|
|
14
|
-
def initialize(**kwargs)
|
|
15
|
-
super
|
|
16
|
-
@issue_type = 'test_case'
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def find_or_create_testcase(test)
|
|
20
|
-
find_testcase(test) || create_issue(test)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def add_issue_link_to_testcase(testcase, issue, test)
|
|
24
|
-
results_section = testcase.description.include?(TEST_CASE_RESULTS_SECTION_TEMPLATE) ? '' : TEST_CASE_RESULTS_SECTION_TEMPLATE
|
|
25
|
-
|
|
26
|
-
gitlab.edit_issue(iid: testcase.iid, options: { description: (testcase.description + results_section + "\n\n#{issue.web_url}") })
|
|
27
|
-
# We are using test.testcase for the url here instead of testcase.web_url since it has the updated test case path
|
|
28
|
-
puts "Added results issue #{issue.web_url} link to test case #{test.testcase}"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def update_testcase(testcase, test)
|
|
32
|
-
puts "Test case labels updated." if update_labels(testcase, test)
|
|
33
|
-
puts "Test case quarantine section updated." if update_quarantine_link(testcase, test)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def find_testcase(test)
|
|
39
|
-
testcase = find_testcase_by_iid(test)
|
|
40
|
-
|
|
41
|
-
if testcase
|
|
42
|
-
testcase = update_issue_title(testcase, test) if issue_title_needs_updating?(testcase, test)
|
|
43
|
-
else
|
|
44
|
-
testcase = find_issue(test)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
testcase
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def find_testcase_by_iid(test)
|
|
51
|
-
iid = testcase_iid_from_url(test.testcase)
|
|
52
|
-
|
|
53
|
-
return unless iid
|
|
54
|
-
|
|
55
|
-
find_issue_by_iid(iid)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def testcase_iid_from_url(url)
|
|
59
|
-
return warn(%(\nPlease update #{url} to test case url")) if url&.include?('/-/issues/')
|
|
60
|
-
|
|
61
|
-
url && url.split('/').last.to_i
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def new_issue_description(test)
|
|
65
|
-
quarantine_section = test.quarantine? && test.quarantine_issue ? "\n\n### Quarantine issue\n\n#{test.quarantine_issue}" : ''
|
|
66
|
-
|
|
67
|
-
"#{super}#{quarantine_section}\n\n#{execution_graph_section(test)}"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def execution_graph_section(test)
|
|
71
|
-
formatted_title = ERB::Util.url_encode(test.name)
|
|
72
|
-
|
|
73
|
-
<<~MKDOWN.strip
|
|
74
|
-
### Executions
|
|
75
|
-
|
|
76
|
-
All Environments:
|
|
77
|
-
<img src="https://dashboards.quality.gitlab.net/render/d-solo/cW0UMgv7k/spec-health?orgId=1&var-run_type=All&var-name=#{formatted_title}&panelId=4&width=1000&height=500" />
|
|
78
|
-
MKDOWN
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def updated_description(testcase, test)
|
|
82
|
-
historical_results_section = testcase.description.match(/### DO NOT EDIT BELOW THIS LINE[\s\S]+/)
|
|
83
|
-
|
|
84
|
-
"#{new_issue_description(test)}\n\n#{historical_results_section}"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def issue_title_needs_updating?(testcase, test)
|
|
88
|
-
super || !testcase.description.include?(execution_graph_section(test)) && !%w[canary production preprod release].include?(pipeline)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def quarantine_link_needs_updating?(testcase, test)
|
|
92
|
-
if test.quarantine? && test.quarantine_issue
|
|
93
|
-
return false if testcase.description.include?(test.quarantine_issue)
|
|
94
|
-
else
|
|
95
|
-
return false unless testcase.description.include?('Quarantine issue')
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
true
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def update_quarantine_link(testcase, test)
|
|
102
|
-
return unless quarantine_link_needs_updating?(testcase, test)
|
|
103
|
-
|
|
104
|
-
new_description = updated_description(testcase, test)
|
|
105
|
-
|
|
106
|
-
gitlab.edit_issue(iid: testcase.iid, options: { description: new_description })
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'active_support/core_ext/enumerable'
|
|
4
|
-
|
|
5
|
-
module Gitlab
|
|
6
|
-
module QA
|
|
7
|
-
module Report
|
|
8
|
-
module ResultsReporterShared
|
|
9
|
-
TEST_CASE_RESULTS_SECTION_TEMPLATE = "\n\n### DO NOT EDIT BELOW THIS LINE\n\nActive and historical test results:"
|
|
10
|
-
|
|
11
|
-
def find_issue(test)
|
|
12
|
-
issues = search_for_issues(test)
|
|
13
|
-
|
|
14
|
-
warn(%(Too many #{issue_type}s found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?
|
|
15
|
-
puts "Found existing #{issue_type}: #{issues.first.web_url}" unless issues.empty?
|
|
16
|
-
|
|
17
|
-
issues.first
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def find_issue_by_iid(iid)
|
|
21
|
-
issues = gitlab.find_issues(iid: iid) do |issue|
|
|
22
|
-
issue.state == 'opened' && issue.issue_type == issue_type
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
warn(%(#{issue_type} iid "#{iid}" not valid)) if issues.empty?
|
|
26
|
-
|
|
27
|
-
issues.first
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def issue_title_needs_updating?(issue, test)
|
|
31
|
-
issue.title.strip != title_from_test(test) && !%w[canary production preprod release].include?(pipeline)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def new_issue_labels(test)
|
|
35
|
-
%w[Quality status::automated]
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def search_term(test)
|
|
39
|
-
%("#{partial_file_path(test.file)}" "#{search_safe(test.name)}")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
|
|
43
|
-
labels = super
|
|
44
|
-
labels |= new_issue_labels(test).to_set
|
|
45
|
-
labels.delete_if { |label| label.start_with?("#{pipeline}::") }
|
|
46
|
-
labels << (test.failures.empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def update_issue_title(issue, test)
|
|
50
|
-
old_title = issue.title.strip
|
|
51
|
-
new_title = title_from_test(test)
|
|
52
|
-
|
|
53
|
-
warn(%(#{issue_type} title needs to be updated from '#{old_title}' to '#{new_title}'))
|
|
54
|
-
|
|
55
|
-
new_description = updated_description(issue, test)
|
|
56
|
-
|
|
57
|
-
gitlab.edit_issue(iid: issue.iid, options: { title: new_title, description: new_description })
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
def search_for_issues(test)
|
|
63
|
-
gitlab.find_issues(options: { search: search_term(test) }) do |issue|
|
|
64
|
-
issue.state == 'opened' && issue.issue_type == issue_type && issue.title.strip == title_from_test(test)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'nokogiri'
|
|
4
|
-
require 'table_print'
|
|
5
|
-
|
|
6
|
-
module Gitlab
|
|
7
|
-
module QA
|
|
8
|
-
module Report
|
|
9
|
-
class SummaryTable
|
|
10
|
-
def self.create(input_files:)
|
|
11
|
-
"```\n#{TablePrint::Printer.table_print(collect_results(input_files))}```\n"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# rubocop:disable Metrics/AbcSize
|
|
15
|
-
def self.collect_results(input_files)
|
|
16
|
-
stage_wise_results = []
|
|
17
|
-
|
|
18
|
-
Dir.glob(input_files).each do |report_file|
|
|
19
|
-
stage_hash = {}
|
|
20
|
-
stage_hash["Dev Stage"] = File.basename(report_file, ".*").capitalize
|
|
21
|
-
|
|
22
|
-
report_stats = Nokogiri::XML(File.open(report_file)).children[0].attributes
|
|
23
|
-
|
|
24
|
-
stage_hash["Total"] = report_stats["tests"].value
|
|
25
|
-
stage_hash["Failures"] = report_stats["failures"].value
|
|
26
|
-
stage_hash["Errors"] = report_stats["errors"].value
|
|
27
|
-
stage_hash["Skipped"] = report_stats["skipped"].value
|
|
28
|
-
stage_hash["Result"] = result_emoji(report_stats)
|
|
29
|
-
|
|
30
|
-
stage_wise_results << stage_hash
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
stage_wise_results
|
|
34
|
-
end
|
|
35
|
-
# rubocop:enable Metrics/AbcSize
|
|
36
|
-
|
|
37
|
-
def self.result_emoji(report_stats)
|
|
38
|
-
report_stats["failures"].value.to_i.positive? || report_stats["errors"].value.to_i.positive? ? "❌" : "✅"
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'active_support/core_ext/object/blank'
|
|
4
|
-
|
|
5
|
-
module Gitlab
|
|
6
|
-
module QA
|
|
7
|
-
module Report
|
|
8
|
-
class TestResult
|
|
9
|
-
def self.from_json(report)
|
|
10
|
-
JsonTestResult.new(report)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.from_junit(report)
|
|
14
|
-
JUnitTestResult.new(report)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
attr_accessor :report, :failures
|
|
18
|
-
|
|
19
|
-
def initialize(report)
|
|
20
|
-
self.report = report
|
|
21
|
-
self.failures = failures_from_exceptions
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def stage
|
|
25
|
-
@stage ||= file[%r{(?:api|browser_ui)/(?:(?:\d+_)?(\w+))}, 1]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def name
|
|
29
|
-
raise NotImplementedError
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def file
|
|
33
|
-
raise NotImplementedError
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def skipped
|
|
37
|
-
raise NotImplementedError
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def failures_from_exceptions
|
|
43
|
-
raise NotImplementedError
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
class JsonTestResult < TestResult
|
|
47
|
-
def name
|
|
48
|
-
report['full_description']
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def file
|
|
52
|
-
report['file_path']
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def status
|
|
56
|
-
report['status']
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def ci_job_url
|
|
60
|
-
report['ci_job_url']
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def skipped
|
|
64
|
-
status == 'pending'
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def testcase
|
|
68
|
-
report['testcase']
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def testcase=(new_testcase)
|
|
72
|
-
report['testcase'] = new_testcase
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def failure_issue
|
|
76
|
-
report['failure_issue']
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def failure_issue=(new_failure_issue)
|
|
80
|
-
report['failure_issue'] = new_failure_issue
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def quarantine?
|
|
84
|
-
# The value for 'quarantine' could be nil, a hash, a string,
|
|
85
|
-
# or true (if the test just has the :quarantine tag)
|
|
86
|
-
# But any non-nil or false value should means the test is in quarantine
|
|
87
|
-
report['quarantine'].present?
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def quarantine_type
|
|
91
|
-
report['quarantine']['type'] if quarantine?
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def quarantine_issue
|
|
95
|
-
report['quarantine']['issue'] if quarantine?
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def screenshot?
|
|
99
|
-
report['screenshot'].present?
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def failure_screenshot
|
|
103
|
-
report['screenshot']['image'] if screenshot?
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def product_group?
|
|
107
|
-
report['product_group'].present?
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def product_group
|
|
111
|
-
report['product_group'] if product_group?
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
# rubocop:disable Metrics/AbcSize
|
|
117
|
-
def failures_from_exceptions
|
|
118
|
-
return [] unless report.key?('exceptions')
|
|
119
|
-
|
|
120
|
-
report['exceptions'].map do |exception|
|
|
121
|
-
spec_file_first_index = exception['backtrace'].rindex do |line|
|
|
122
|
-
line.include?(File.basename(report['file_path']))
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
exception['message'].gsub!(/(private_token=)[\w-]+/, '********')
|
|
126
|
-
Array(exception['message_lines']).each { |line| line.gsub!(/(private_token=)([\w-]+)/, '********') }
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
'message' => "#{exception['class']}: #{exception['message']}",
|
|
130
|
-
'message_lines' => exception['message_lines'],
|
|
131
|
-
'stacktrace' => "#{format_message_lines(exception['message_lines'])}\n#{exception['backtrace'].slice(0..spec_file_first_index).join("\n")}",
|
|
132
|
-
'correlation_id' => exception['correlation_id']
|
|
133
|
-
}
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def format_message_lines(message_lines)
|
|
138
|
-
message_lines.is_a?(Array) ? message_lines.join("\n") : message_lines
|
|
139
|
-
end
|
|
140
|
-
# rubocop:enable Metrics/AbcSize
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
class JUnitTestResult < TestResult
|
|
144
|
-
def name
|
|
145
|
-
report['name']
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def file
|
|
149
|
-
report['file']
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def skipped
|
|
153
|
-
report.search('skipped').any?
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
attr_accessor :testcase # Ignore it for now
|
|
157
|
-
|
|
158
|
-
private
|
|
159
|
-
|
|
160
|
-
# rubocop:disable Metrics/AbcSize
|
|
161
|
-
def failures_from_exceptions
|
|
162
|
-
failures = report.search('failure')
|
|
163
|
-
return [] if failures.empty?
|
|
164
|
-
|
|
165
|
-
failures.map do |exception|
|
|
166
|
-
trace = exception.content.split("\n").map(&:strip)
|
|
167
|
-
spec_file_first_index = trace.rindex do |line|
|
|
168
|
-
line.include?(File.basename(report['file']))
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
exception['message'].gsub!(/(private_token=)[\w-]+/, '********')
|
|
172
|
-
|
|
173
|
-
{
|
|
174
|
-
'message' => "#{exception['type']}: #{exception['message']}",
|
|
175
|
-
'stacktrace' => trace.slice(0..spec_file_first_index).join("\n")
|
|
176
|
-
}
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
# rubocop:enable Metrics/AbcSize
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'nokogiri'
|
|
4
|
-
require 'json'
|
|
5
|
-
require 'active_support/core_ext/object/blank'
|
|
6
|
-
|
|
7
|
-
module Gitlab
|
|
8
|
-
module QA
|
|
9
|
-
module Report
|
|
10
|
-
class UpdateScreenshotPath
|
|
11
|
-
def initialize(files:)
|
|
12
|
-
@files = files
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
REGEX = %r{(?<gitlab_qa_run>gitlab-qa-run-.*?(?=\/))\/(?<gitlab_ce_ee_qa>gitlab-(ee|ce)-qa-.*?(?=\/))}
|
|
16
|
-
CONTAINER_PATH = File.join(Docker::Volumes::QA_CONTAINER_WORKDIR, 'tmp').freeze
|
|
17
|
-
|
|
18
|
-
def invoke!
|
|
19
|
-
Dir.glob(@files).each do |rspec_report_file|
|
|
20
|
-
match_data = rspec_report_file.match(REGEX)
|
|
21
|
-
artifact_path = "#{match_data[:gitlab_qa_run]}/#{match_data[:gitlab_ce_ee_qa]}"
|
|
22
|
-
|
|
23
|
-
xml_report = rewrite_each_xml_screenshot_path(rspec_report_file, artifact_path)
|
|
24
|
-
|
|
25
|
-
File.write(rspec_report_file, xml_report)
|
|
26
|
-
|
|
27
|
-
puts "Saved #{rspec_report_file}"
|
|
28
|
-
|
|
29
|
-
json_rspec_report_file = rspec_report_file.gsub('.xml', '.json')
|
|
30
|
-
json_report = rewrite_each_json_screenshot_path(json_rspec_report_file, artifact_path)
|
|
31
|
-
|
|
32
|
-
File.write(json_rspec_report_file, json_report)
|
|
33
|
-
|
|
34
|
-
puts "Saved #{json_rspec_report_file}"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def rewrite_each_xml_screenshot_path(rspec_report_file, artifact_path)
|
|
41
|
-
report = Nokogiri::XML(File.open(rspec_report_file))
|
|
42
|
-
|
|
43
|
-
report.xpath('//system-out').each do |system_out|
|
|
44
|
-
system_out.content = system_out.content.gsub(CONTAINER_PATH, artifact_path)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
report.to_s
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def rewrite_each_json_screenshot_path(json_rspec_report_file, artifact_path)
|
|
51
|
-
report = JSON.parse(File.read(json_rspec_report_file))
|
|
52
|
-
examples = report['examples']
|
|
53
|
-
|
|
54
|
-
examples.each do |example|
|
|
55
|
-
example['screenshot']['image'] = example['screenshot']['image'].gsub(CONTAINER_PATH, artifact_path) if example['screenshot'].present?
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
JSON.pretty_generate(report)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|