gitlab_quality-test_tooling 0.1.0 → 0.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/.rubocop.yml +9 -4
- data/Gemfile.lock +1 -1
- data/Guardfile +0 -22
- data/README.md +150 -9
- data/exe/generate-test-session +50 -0
- data/exe/post-to-slack +58 -0
- data/exe/prepare-stage-reports +38 -0
- data/exe/relate-failure-issue +59 -0
- data/exe/report-results +56 -0
- data/exe/update-screenshot-paths +38 -0
- data/lib/gitlab_quality/test_tooling/gitlab_issue_client.rb +194 -0
- data/lib/gitlab_quality/test_tooling/gitlab_issue_dry_client.rb +26 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/find_set_dri.rb +51 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/results_reporter.rb +75 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/utils.rb +49 -0
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +275 -0
- data/lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb +78 -0
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +377 -0
- data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +134 -0
- data/lib/gitlab_quality/test_tooling/report/report_results.rb +83 -0
- data/lib/gitlab_quality/test_tooling/report/results_in_issues.rb +130 -0
- data/lib/gitlab_quality/test_tooling/report/results_in_testcases.rb +113 -0
- data/lib/gitlab_quality/test_tooling/report/update_screenshot_path.rb +81 -0
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +113 -0
- data/lib/gitlab_quality/test_tooling/runtime/logger.rb +92 -0
- data/lib/gitlab_quality/test_tooling/runtime/token_finder.rb +44 -0
- data/lib/gitlab_quality/test_tooling/slack/post_to_slack.rb +36 -0
- data/lib/gitlab_quality/test_tooling/summary_table.rb +41 -0
- data/lib/gitlab_quality/test_tooling/support/http_request.rb +34 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/json_log_finder.rb +65 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/api_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/application_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/exception_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/graphql_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/log.rb +38 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/api_log.rb +34 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/application_log.rb +27 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/exception_log.rb +23 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/graphql_log.rb +30 -0
- data/lib/gitlab_quality/test_tooling/system_logs/shared_fields.rb +29 -0
- data/lib/gitlab_quality/test_tooling/system_logs/system_logs_formatter.rb +65 -0
- data/lib/gitlab_quality/test_tooling/test_results/base_test_results.rb +39 -0
- data/lib/gitlab_quality/test_tooling/test_results/builder.rb +35 -0
- data/lib/gitlab_quality/test_tooling/test_results/j_unit_test_results.rb +27 -0
- data/lib/gitlab_quality/test_tooling/test_results/json_test_results.rb +29 -0
- data/lib/gitlab_quality/test_tooling/test_results/test_result.rb +184 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- data/lib/gitlab_quality/test_tooling.rb +11 -2
- metadata +51 -3
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module SystemLogs
|
8
|
+
module Finders
|
9
|
+
class JsonLogFinder
|
10
|
+
def initialize(base_path, file_path)
|
11
|
+
@base_path = base_path
|
12
|
+
@file_path = file_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(correlation_id)
|
16
|
+
log_file_path = "#{@base_path}/#{@file_path}"
|
17
|
+
logs = []
|
18
|
+
|
19
|
+
if File.exist?(log_file_path) && !correlation_id.nil?
|
20
|
+
File.foreach(log_file_path) do |line|
|
21
|
+
begin
|
22
|
+
json_line = JSON.parse(line, symbolize_names: true)
|
23
|
+
rescue JSON::ParserError
|
24
|
+
Runtime::Logger.debug("JsonLogFinder#find attempted to parse invalid JSON: #{line}")
|
25
|
+
|
26
|
+
next
|
27
|
+
end
|
28
|
+
|
29
|
+
if (json_line[:correlation_id])&.casecmp?(correlation_id)
|
30
|
+
normalized_line = normalize_keys(json_line)
|
31
|
+
logs << new_log(normalized_line)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
logs
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_log(_data)
|
40
|
+
raise 'abstract method new_log must be defined!'
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def normalize_keys(json_line)
|
46
|
+
normalized_hash = {}
|
47
|
+
|
48
|
+
json_line.each_key do |old_key|
|
49
|
+
key_string = old_key.to_s
|
50
|
+
|
51
|
+
if key_string.include?('.')
|
52
|
+
normalized_key = key_string.tr('.', '_').to_sym
|
53
|
+
normalized_hash[normalized_key] = json_line[old_key]
|
54
|
+
else
|
55
|
+
normalized_hash[old_key] = json_line[old_key]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
normalized_hash
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module Finders
|
7
|
+
module Rails
|
8
|
+
class ApiLogFinder < JsonLogFinder
|
9
|
+
def initialize(base_path, file_path = 'gitlab-rails/api_json.log')
|
10
|
+
super(base_path, file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_log(data)
|
14
|
+
LogTypes::Rails::ApiLog.new(data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module Finders
|
7
|
+
module Rails
|
8
|
+
class ApplicationLogFinder < JsonLogFinder
|
9
|
+
def initialize(base_path, file_path = 'gitlab-rails/application_json.log')
|
10
|
+
super(base_path, file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_log(data)
|
14
|
+
LogTypes::Rails::ApplicationLog.new(data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module Finders
|
7
|
+
module Rails
|
8
|
+
class ExceptionLogFinder < JsonLogFinder
|
9
|
+
def initialize(base_path, file_path = 'gitlab-rails/exceptions_json.log')
|
10
|
+
super(base_path, file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_log(data)
|
14
|
+
LogTypes::Rails::ExceptionLog.new(data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module Finders
|
7
|
+
module Rails
|
8
|
+
class GraphqlLogFinder < JsonLogFinder
|
9
|
+
def initialize(base_path, file_path = 'gitlab-rails/graphql_json.log')
|
10
|
+
super(base_path, file_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_log(data)
|
14
|
+
LogTypes::Rails::GraphqlLog.new(data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module LogTypes
|
7
|
+
class Log
|
8
|
+
def initialize(name, data)
|
9
|
+
@name = name
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name, :data
|
14
|
+
|
15
|
+
def summary_fields
|
16
|
+
[
|
17
|
+
:severity,
|
18
|
+
:correlation_id,
|
19
|
+
:time,
|
20
|
+
:message
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def summary
|
25
|
+
summary = {}
|
26
|
+
|
27
|
+
summary_fields.each do |field|
|
28
|
+
value = data[field]
|
29
|
+
summary[field] = value unless value.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
summary
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module LogTypes
|
7
|
+
module Rails
|
8
|
+
class ApiLog < Log
|
9
|
+
include SharedFields::Exception
|
10
|
+
include SharedFields::Meta
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
super('Rails API', data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def summary_fields
|
17
|
+
super.concat(
|
18
|
+
[
|
19
|
+
:method,
|
20
|
+
:path,
|
21
|
+
:status,
|
22
|
+
:params,
|
23
|
+
:api_error
|
24
|
+
],
|
25
|
+
exception_fields,
|
26
|
+
meta_fields
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module LogTypes
|
7
|
+
module Rails
|
8
|
+
class ApplicationLog < Log
|
9
|
+
include SharedFields::Exception
|
10
|
+
include SharedFields::Meta
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
super('Rails Application', data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def summary_fields
|
17
|
+
super.concat(
|
18
|
+
exception_fields,
|
19
|
+
meta_fields
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module LogTypes
|
7
|
+
module Rails
|
8
|
+
class ExceptionLog < Log
|
9
|
+
include SharedFields::Exception
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super('Rails Exceptions', data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def summary_fields
|
16
|
+
super.concat(exception_fields)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module LogTypes
|
7
|
+
module Rails
|
8
|
+
class GraphqlLog < Log
|
9
|
+
include SharedFields::Meta
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
super('Rails GraphQL', data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def summary_fields
|
16
|
+
super.concat(
|
17
|
+
[
|
18
|
+
:operation_name,
|
19
|
+
:query_string,
|
20
|
+
:variables
|
21
|
+
],
|
22
|
+
meta_fields
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
module SharedFields
|
7
|
+
module Meta
|
8
|
+
def meta_fields
|
9
|
+
[
|
10
|
+
:meta_user,
|
11
|
+
:meta_project,
|
12
|
+
:meta_caller_id
|
13
|
+
]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Exception
|
18
|
+
def exception_fields
|
19
|
+
[
|
20
|
+
:exception_class,
|
21
|
+
:exception_message,
|
22
|
+
:exception_backtrace
|
23
|
+
]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module SystemLogs
|
6
|
+
class SystemLogsFormatter
|
7
|
+
NUM_OF_LOG_SECTIONS = 4
|
8
|
+
|
9
|
+
def initialize(base_paths, correlation_id)
|
10
|
+
@base_paths = base_paths
|
11
|
+
@correlation_id = correlation_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def system_logs_summary_markdown
|
15
|
+
log_sections = Array.new(NUM_OF_LOG_SECTIONS) { [] }
|
16
|
+
|
17
|
+
@base_paths.each do |base_path|
|
18
|
+
all_logs = [
|
19
|
+
Finders::Rails::ApiLogFinder.new(base_path).find(@correlation_id),
|
20
|
+
Finders::Rails::ExceptionLogFinder.new(base_path).find(@correlation_id),
|
21
|
+
Finders::Rails::ApplicationLogFinder.new(base_path).find(@correlation_id),
|
22
|
+
Finders::Rails::GraphqlLogFinder.new(base_path).find(@correlation_id)
|
23
|
+
]
|
24
|
+
|
25
|
+
create_log_summary_sections!(all_logs, log_sections)
|
26
|
+
end
|
27
|
+
|
28
|
+
log_sections.prepend('### System Logs') unless log_sections.all?(&:empty?)
|
29
|
+
log_sections.join("\n").rstrip
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_log_summary_sections!(all_logs, sections)
|
35
|
+
sections.zip(all_logs) do |section, logs|
|
36
|
+
unless logs.empty?
|
37
|
+
section_title = "\n#### #{logs.first.name}"
|
38
|
+
section.append(section_title) unless section.include?(section_title)
|
39
|
+
section.append(create_log_summaries(logs))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_log_summaries(logs)
|
45
|
+
section = []
|
46
|
+
|
47
|
+
logs.each do |log|
|
48
|
+
log_summary = <<~MARKDOWN.chomp
|
49
|
+
<details><summary>Click to expand</summary>
|
50
|
+
|
51
|
+
```json
|
52
|
+
#{JSON.pretty_generate(log.summary)}
|
53
|
+
```
|
54
|
+
</details>
|
55
|
+
MARKDOWN
|
56
|
+
|
57
|
+
section.append(log_summary)
|
58
|
+
end
|
59
|
+
|
60
|
+
section.join("\n\n")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module TestResults
|
6
|
+
class BaseTestResults
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
@results = parse
|
14
|
+
@testcases = process
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
testcases.each(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def write
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :results, :testcases
|
28
|
+
|
29
|
+
def parse
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def process
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module TestResults
|
6
|
+
class Builder
|
7
|
+
def initialize(file_glob)
|
8
|
+
@file_glob = file_glob
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_results_per_file
|
12
|
+
Dir.glob(file_glob).each do |path|
|
13
|
+
extension = File.extname(path)
|
14
|
+
|
15
|
+
test_results =
|
16
|
+
case extension
|
17
|
+
when '.json'
|
18
|
+
TestResults::JsonTestResults.new(path)
|
19
|
+
when '.xml'
|
20
|
+
TestResults::JUnitTestResults.new(path)
|
21
|
+
else
|
22
|
+
raise "Unknown extension #{extension}"
|
23
|
+
end
|
24
|
+
|
25
|
+
yield test_results
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :file_glob
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module TestResults
|
8
|
+
class JUnitTestResults < BaseTestResults
|
9
|
+
def write
|
10
|
+
# Ignore it for now
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def parse
|
16
|
+
Nokogiri::XML.parse(File.read(path))
|
17
|
+
end
|
18
|
+
|
19
|
+
def process
|
20
|
+
results.xpath('//testcase').map do |test|
|
21
|
+
TestResult.from_junit(test)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module TestResults
|
8
|
+
class JsonTestResults < BaseTestResults
|
9
|
+
def write
|
10
|
+
json = results.merge('examples' => testcases.map(&:report))
|
11
|
+
|
12
|
+
File.write(path, JSON.pretty_generate(json))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse
|
18
|
+
JSON.parse(File.read(path))
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
results['examples'].map do |test|
|
23
|
+
TestResult.from_json(test)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|