gitlab_quality-test_tooling 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|