blinka-reporter 0.5.2 → 0.7.0
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/.github/dependabot.yml +18 -0
- data/.github/release.yml +9 -0
- data/.github/workflows/main.yml +50 -0
- data/.gitignore +29 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +167 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +163 -0
- data/Rakefile +7 -0
- data/bin/blinka_reporter +7 -0
- data/bin/setup +15 -0
- data/blinka_reporter.gemspec +41 -0
- data/lib/blinka_reporter/blinka.rb +172 -0
- data/lib/blinka_reporter/cli.rb +66 -0
- data/lib/blinka_reporter/client.rb +128 -0
- data/lib/blinka_reporter/config.rb +37 -0
- data/lib/blinka_reporter/error.rb +3 -0
- data/lib/blinka_reporter/minitest_adapter.rb +92 -0
- data/lib/blinka_reporter/tap.rb +51 -0
- data/lib/blinka_reporter/version.rb +1 -1
- data/lib/blinka_reporter.rb +4 -0
- data/lib/minitest/blinka_plugin.rb +14 -82
- data/package.json +22 -0
- data/yarn.lock +481 -0
- metadata +41 -5
- data/lib/blinka_client.rb +0 -195
- data/lib/blinka_minitest.rb +0 -90
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'blinka_reporter/error'
|
3
|
+
|
4
|
+
module BlinkaReporter
|
5
|
+
class Blinka
|
6
|
+
SUPPORTED_MIME_TYPES = {
|
7
|
+
jpg: 'image/jpeg',
|
8
|
+
jpeg: 'image/jpeg',
|
9
|
+
png: 'image/png'
|
10
|
+
}
|
11
|
+
|
12
|
+
include HTTParty
|
13
|
+
|
14
|
+
def self.report(data:, config:)
|
15
|
+
Blinka.new(data: data, config: config).report
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(data:, config:)
|
19
|
+
@data = data
|
20
|
+
@config = config
|
21
|
+
self.class.base_uri("#{@config.host}/api/v1")
|
22
|
+
end
|
23
|
+
|
24
|
+
def report
|
25
|
+
if ENV.fetch('BLINKA_ALLOW_WEBMOCK_DISABLE', 'true') == 'true' &&
|
26
|
+
defined?(WebMock) && WebMock.respond_to?(:disable!)
|
27
|
+
WebMock.disable!
|
28
|
+
end
|
29
|
+
|
30
|
+
@config.validate_blinka
|
31
|
+
self.authenticate
|
32
|
+
|
33
|
+
results =
|
34
|
+
@data
|
35
|
+
.fetch(:results, [])
|
36
|
+
.map do |result|
|
37
|
+
if !result[:image].nil?
|
38
|
+
result[:image] = Blinka.upload_image(filepath: result[:image])
|
39
|
+
result
|
40
|
+
else
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
body = {
|
46
|
+
report: {
|
47
|
+
repository: @config.repository,
|
48
|
+
tag: @config.tag,
|
49
|
+
commit: @config.commit,
|
50
|
+
metadata: {
|
51
|
+
total_time: @data[:total_time],
|
52
|
+
nbr_tests: @data[:nbr_tests],
|
53
|
+
nbr_assertions: @data[:nbr_assertions],
|
54
|
+
seed: @data[:seed]
|
55
|
+
}.compact,
|
56
|
+
results: results
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
response =
|
61
|
+
self.class.post(
|
62
|
+
'/report',
|
63
|
+
body: body.to_json,
|
64
|
+
headers: {
|
65
|
+
'Content-Type' => 'application/json',
|
66
|
+
'Authorization' => "Bearer #{@jwt_token}"
|
67
|
+
}
|
68
|
+
)
|
69
|
+
case response.code
|
70
|
+
when 200
|
71
|
+
puts "Reported #{@data[:nbr_tests]} tests of commit #{@config.commit}!"
|
72
|
+
else
|
73
|
+
raise(
|
74
|
+
BlinkaReporter::Error,
|
75
|
+
"Could not report, got response code #{response.code}"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
rescue => error
|
79
|
+
raise(BlinkaReporter::Error, <<-EOS)
|
80
|
+
BLINKA:
|
81
|
+
Failed to create report because of #{error.class} with message:
|
82
|
+
#{error.message}
|
83
|
+
EOS
|
84
|
+
ensure
|
85
|
+
WebMock.enable! if defined?(WebMock) && WebMock.respond_to?(:enable!)
|
86
|
+
end
|
87
|
+
|
88
|
+
def authenticate
|
89
|
+
response =
|
90
|
+
self.class.post(
|
91
|
+
'/authentication',
|
92
|
+
body: {
|
93
|
+
token_id: @config.team_id,
|
94
|
+
token_secret: @config.team_secret
|
95
|
+
}.to_json,
|
96
|
+
headers: { 'Content-Type' => 'application/json' }
|
97
|
+
)
|
98
|
+
case response.code
|
99
|
+
when 200
|
100
|
+
@jwt_token = JSON.parse(response.body).dig('auth_token')
|
101
|
+
else
|
102
|
+
raise(
|
103
|
+
BlinkaReporter::Error,
|
104
|
+
"Could not authenticate to API #{response.code}"
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.upload_image(filepath:)
|
110
|
+
return unless File.exist?(filepath)
|
111
|
+
|
112
|
+
file = File.open(filepath)
|
113
|
+
filename = File.basename(filepath)
|
114
|
+
extension = File.extname(filepath).delete('.').to_sym
|
115
|
+
content_type = SUPPORTED_MIME_TYPES[extension]
|
116
|
+
return if content_type.nil?
|
117
|
+
|
118
|
+
presigned_post =
|
119
|
+
Blinka.presign_image(filename: filename, content_type: content_type)
|
120
|
+
Blinka.upload_to_storage(presigned_post: presigned_post, file: file)
|
121
|
+
|
122
|
+
puts "Uploaded: #{filename}"
|
123
|
+
Blinka.to_shrine_object(
|
124
|
+
presigned_post: presigned_post,
|
125
|
+
file: file,
|
126
|
+
filename: filename
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.presign_image(filename:, content_type:)
|
131
|
+
response =
|
132
|
+
self.get(
|
133
|
+
'/presign',
|
134
|
+
body: { filename: filename, content_type: content_type }
|
135
|
+
)
|
136
|
+
|
137
|
+
case response.code
|
138
|
+
when 200
|
139
|
+
JSON.parse(response.body)
|
140
|
+
else
|
141
|
+
raise(BlinkaReporter::Error, 'Could not presign file')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.upload_to_storage(presigned_post:, file:)
|
146
|
+
url = URI.parse(presigned_post.fetch('url'))
|
147
|
+
|
148
|
+
body = presigned_post['fields'].merge({ 'file' => file.read })
|
149
|
+
response = HTTParty.post(url, multipart: true, body: body)
|
150
|
+
|
151
|
+
case response.code
|
152
|
+
when 204
|
153
|
+
true
|
154
|
+
else
|
155
|
+
raise(BlinkaReporter::Error, 'Could not upload file to storage')
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.to_shrine_object(presigned_post:, file:, filename:)
|
160
|
+
storage, idx = presigned_post.dig('fields', 'key').split('/')
|
161
|
+
{
|
162
|
+
"id": idx,
|
163
|
+
"storage": storage,
|
164
|
+
"metadata": {
|
165
|
+
"size": file.size,
|
166
|
+
"filename": filename,
|
167
|
+
"mime_type": presigned_post.dig('fields', 'Content-Type')
|
168
|
+
}
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'blinka_reporter/client'
|
2
|
+
require 'blinka_reporter/version'
|
3
|
+
|
4
|
+
module BlinkaReporter
|
5
|
+
class Cli
|
6
|
+
def self.run(argv)
|
7
|
+
if (argv.index('--help') || -1) >= 0
|
8
|
+
puts(<<~EOS)
|
9
|
+
blinka_reporter version #{BlinkaReporter::VERSION}
|
10
|
+
|
11
|
+
Options:
|
12
|
+
--path <path>: Path to test results file, can be supplied multiple times to combine results
|
13
|
+
- ./blinka_results.json blinka json format
|
14
|
+
- ./rspec.xml from https://github.com/sj26/rspec_junit_formatter
|
15
|
+
|
16
|
+
--tap: Flag for outputting test results in TAP-protocol, helpful on Heroku CI
|
17
|
+
--blinka: Flag for reporting test results to blinka.app, requires also supplying:
|
18
|
+
- --team-id
|
19
|
+
- --team-secret
|
20
|
+
- --repository
|
21
|
+
- --commit
|
22
|
+
--team-id <team-id>: Blinka team id, only used with --blinka
|
23
|
+
--team-secret <team-secret>: Blinka team secret, only used with --blinka
|
24
|
+
--commit <commit>: The commit hash to report
|
25
|
+
--tag <tag>: The tag for the run, for example to separate a test matrix
|
26
|
+
--repository <repository>: The Github repository
|
27
|
+
--host <host>: Override Blink host to send report
|
28
|
+
|
29
|
+
EOS
|
30
|
+
return 0
|
31
|
+
end
|
32
|
+
|
33
|
+
tap = (argv.index('--tap') || -1) >= 0
|
34
|
+
|
35
|
+
paths = argv_value_for(argv, '--path')
|
36
|
+
|
37
|
+
blinka = (argv.index('--blinka') || -1) >= 0
|
38
|
+
commit = argv_value_for(argv, '--commit')&.first
|
39
|
+
repository = argv_value_for(argv, '--repository')&.first
|
40
|
+
tag = argv_value_for(argv, '--tag')&.first
|
41
|
+
team_id = argv_value_for(argv, '--team-id')&.first
|
42
|
+
team_secret = argv_value_for(argv, '--team-secret')&.first
|
43
|
+
host = argv_value_for(argv, '--host')&.first
|
44
|
+
|
45
|
+
client = BlinkaReporter::Client.new
|
46
|
+
data = client.parse(paths: paths)
|
47
|
+
config =
|
48
|
+
BlinkaReporter::Config.new(
|
49
|
+
tag: tag,
|
50
|
+
commit: commit,
|
51
|
+
team_id: team_id,
|
52
|
+
team_secret: team_secret,
|
53
|
+
repository: repository,
|
54
|
+
host: host
|
55
|
+
)
|
56
|
+
client.report(data: data, config: config, tap: tap, blinka: blinka)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.argv_value_for(argv, option_name)
|
60
|
+
argv
|
61
|
+
.each_index
|
62
|
+
.select { |index| argv[index] == option_name }
|
63
|
+
.map { |index| argv[index + 1] }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'ox'
|
3
|
+
|
4
|
+
require 'blinka_reporter/blinka'
|
5
|
+
require 'blinka_reporter/config'
|
6
|
+
require 'blinka_reporter/error'
|
7
|
+
require 'blinka_reporter/tap'
|
8
|
+
|
9
|
+
module BlinkaReporter
|
10
|
+
class Client
|
11
|
+
def parse(paths: nil)
|
12
|
+
paths ||= ['./blinka_results.json']
|
13
|
+
paths = Array(paths)
|
14
|
+
paths.each do |path|
|
15
|
+
unless File.exist?(path)
|
16
|
+
raise(
|
17
|
+
BlinkaReporter::Error,
|
18
|
+
"Could not find #{path}, make sure the path is correct."
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
merge_results(
|
24
|
+
paths.map do |path|
|
25
|
+
if path.end_with?('.xml')
|
26
|
+
parse_xml(path: path)
|
27
|
+
elsif path.end_with?('.json')
|
28
|
+
parse_json(path: path)
|
29
|
+
else
|
30
|
+
raise(
|
31
|
+
BlinkaReporter::Error,
|
32
|
+
"Unknown format of #{path}, needs to be .json or .xml"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def report(data:, blinka: false, tap: false, config: nil)
|
40
|
+
BlinkaReporter::Tap.report(data) if tap
|
41
|
+
BlinkaReporter::Blinka.report(config: config, data: data) if blinka
|
42
|
+
0
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def merge_results(data_array)
|
48
|
+
data = { total_time: 0, nbr_tests: 0, nbr_assertions: 0, results: [] }
|
49
|
+
data_array.each do |result|
|
50
|
+
data[:total_time] += result[:total_time] || 0
|
51
|
+
data[:nbr_tests] += result[:nbr_tests] || 0
|
52
|
+
data[:nbr_assertions] += result[:nbr_assertions] || 0
|
53
|
+
data[:results] += result[:results] || []
|
54
|
+
end
|
55
|
+
data
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_json(path:)
|
59
|
+
JSON.parse(File.open(path).read, symbolize_names: true)
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_xml(path:)
|
63
|
+
data = Ox.load_file(path, { symbolize_keys: true, skip: :skip_none })
|
64
|
+
test_suite = data.root
|
65
|
+
unless test_suite.name == 'testsuite'
|
66
|
+
raise("Root element is not <testsuite>, instead #{test_suite.name}")
|
67
|
+
end
|
68
|
+
|
69
|
+
properties = test_suite.nodes.select { |node| node.name == 'properties' }
|
70
|
+
test_cases = test_suite.nodes.select { |node| node.name == 'testcase' }
|
71
|
+
{
|
72
|
+
nbr_tests: Integer(test_suite.tests || 0),
|
73
|
+
total_time: Float(test_suite.time),
|
74
|
+
seed: xml_seed(properties),
|
75
|
+
results: xml_test_cases(test_cases)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def xml_seed(ox_properties)
|
80
|
+
ox_properties.each do |property|
|
81
|
+
property.nodes.each do |node|
|
82
|
+
return node.attributes[:value] if node.attributes[:name] == 'seed'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# Kind is extracted from the second part of spec.models.customer_spec
|
89
|
+
def xml_test_cases(test_cases)
|
90
|
+
test_cases.map do |test_case|
|
91
|
+
result = {
|
92
|
+
kind: Array(test_case.attributes[:classname]&.split('.'))[1],
|
93
|
+
name: test_case.attributes[:name],
|
94
|
+
path: test_case.attributes[:file]&.delete_prefix('./'),
|
95
|
+
time: Float(test_case.attributes[:time] || 0)
|
96
|
+
}
|
97
|
+
if test_case.nodes.any?
|
98
|
+
skipped = test_case.nodes.any? { |node| node.name == 'skipped' }
|
99
|
+
result[:result] = 'skip' if skipped
|
100
|
+
failure =
|
101
|
+
test_case.nodes.select { |node| node.name == 'failure' }.first
|
102
|
+
if failure
|
103
|
+
result[:result] = 'fail'
|
104
|
+
|
105
|
+
# Needs to be double quotation marks to work properly
|
106
|
+
result[:backtrace] = failure.text.split("\n")
|
107
|
+
result[:image] = get_image_path(result[:backtrace])
|
108
|
+
result[:message] = failure.attributes[:message]
|
109
|
+
end
|
110
|
+
else
|
111
|
+
result[:result] = 'pass'
|
112
|
+
end
|
113
|
+
result
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_image_path(backtrace)
|
118
|
+
backtrace.each do |text|
|
119
|
+
path = /^(\[Screenshot\]|\[Screenshot Image\]):\s([\S]*)$/.match(text)
|
120
|
+
next if path.nil?
|
121
|
+
path = path[-1]
|
122
|
+
next unless File.exist?(path)
|
123
|
+
return path
|
124
|
+
end
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'blinka_reporter/error'
|
2
|
+
|
3
|
+
module BlinkaReporter
|
4
|
+
class Config
|
5
|
+
attr_reader(:commit, :host, :repository, :tag, :team_id, :team_secret)
|
6
|
+
DEFAULT_HOST = 'https://www.blinka.app'
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
tag:,
|
10
|
+
commit:,
|
11
|
+
repository:,
|
12
|
+
host: nil,
|
13
|
+
team_id:,
|
14
|
+
team_secret:
|
15
|
+
)
|
16
|
+
@commit = commit || find_commit
|
17
|
+
@host = host || DEFAULT_HOST
|
18
|
+
@repository = repository
|
19
|
+
@tag = tag
|
20
|
+
@team_id = team_id
|
21
|
+
@team_secret = team_secret
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_blinka
|
25
|
+
required = [@team_id, @team_secret, @repository]
|
26
|
+
if required.include?(nil) || required.include?('')
|
27
|
+
raise(BlinkaReporter::Error, <<~EOS)
|
28
|
+
Missing configuration, make sure to set --team-id, --team-secret, --repository
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_commit
|
34
|
+
ENV.fetch('HEROKU_TEST_RUN_COMMIT_VERSION', `git rev-parse HEAD`.chomp)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module BlinkaReporter
|
2
|
+
class MinitestAdapter
|
3
|
+
def initialize(test_result)
|
4
|
+
@test_result = test_result
|
5
|
+
end
|
6
|
+
|
7
|
+
def path
|
8
|
+
@path ||= source_location.first.gsub(Dir.getwd, '').delete_prefix('/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def line
|
12
|
+
@line ||= source_location.last
|
13
|
+
end
|
14
|
+
|
15
|
+
# Handle broken API in Minitest between 5.10 and 5.11
|
16
|
+
# https://github.com/minitest-reporters/minitest-reporters/blob/e9092460b5a5cf5ca9eb375428217cbb2a7f6dbb/lib/minitest/reporters/default_reporter.rb#L159
|
17
|
+
def source_location
|
18
|
+
@source_location ||=
|
19
|
+
if @test_result.respond_to?(:klass)
|
20
|
+
@test_result.source_location
|
21
|
+
else
|
22
|
+
@test_result.method(@test_result.name).source_location
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def kind
|
27
|
+
parts = self.path.gsub('test/', '').split('/')
|
28
|
+
parts.length > 1 ? parts.first : 'general'
|
29
|
+
end
|
30
|
+
|
31
|
+
def message
|
32
|
+
failure = @test_result.failure
|
33
|
+
return unless failure
|
34
|
+
"#{failure.error.class}: #{failure.error.message}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def backtrace
|
38
|
+
return unless @test_result.failure
|
39
|
+
Minitest.filter_backtrace(@test_result.failure.backtrace)
|
40
|
+
end
|
41
|
+
|
42
|
+
def result
|
43
|
+
if @test_result.error?
|
44
|
+
:error
|
45
|
+
elsif @test_result.skipped?
|
46
|
+
:skip
|
47
|
+
elsif @test_result.failure
|
48
|
+
:fail
|
49
|
+
else
|
50
|
+
:pass
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def time
|
55
|
+
@test_result.time
|
56
|
+
end
|
57
|
+
|
58
|
+
def name
|
59
|
+
@test_result.name
|
60
|
+
end
|
61
|
+
|
62
|
+
def image
|
63
|
+
return unless kind == 'system'
|
64
|
+
|
65
|
+
image_path =
|
66
|
+
if defined?(Capybara) && Capybara.respond_to?(:save_path) &&
|
67
|
+
Capybara.save_path.present?
|
68
|
+
"#{Capybara.save_path}/failures_#{name}.png"
|
69
|
+
else
|
70
|
+
"./tmp/screenshots/failures_#{name}.png"
|
71
|
+
end
|
72
|
+
|
73
|
+
return unless File.exist?(image_path)
|
74
|
+
|
75
|
+
image_path
|
76
|
+
end
|
77
|
+
|
78
|
+
def report
|
79
|
+
{
|
80
|
+
backtrace: backtrace,
|
81
|
+
message: message,
|
82
|
+
line: line,
|
83
|
+
image: image,
|
84
|
+
kind: kind,
|
85
|
+
name: name,
|
86
|
+
path: path,
|
87
|
+
result: result,
|
88
|
+
time: time
|
89
|
+
}.compact
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module BlinkaReporter
|
2
|
+
class Tap
|
3
|
+
# Based on https://github.com/kern/minitest-reporters/blob/master/lib/minitest/reporters/progress_reporter.rb
|
4
|
+
# Tries to adhere to https://testanything.org/tap-specification.html
|
5
|
+
TAP_COMMENT_PAD = 8
|
6
|
+
attr_reader(:data)
|
7
|
+
|
8
|
+
def self.report(data)
|
9
|
+
Tap.new(data).report
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
results = Array(data[:results])
|
14
|
+
return if results.size == 0
|
15
|
+
|
16
|
+
@data = <<~REPORT
|
17
|
+
TAP version 13
|
18
|
+
1..#{results.size}
|
19
|
+
#{test_results(results)}
|
20
|
+
REPORT
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_results(results)
|
24
|
+
report = []
|
25
|
+
results.each_with_index do |test, index|
|
26
|
+
test_str = "#{test[:path]} - #{test[:name].tr('#', '_')}"
|
27
|
+
result = test[:result]
|
28
|
+
if result == 'pass'
|
29
|
+
report << "ok #{index + 1} - #{test_str}"
|
30
|
+
elsif result == 'skip'
|
31
|
+
report << "ok #{index + 1} # skip: #{test_str}"
|
32
|
+
elsif result == 'fail'
|
33
|
+
report << "not ok #{index + 1} - failed: #{test_str}"
|
34
|
+
test[:message].split('\n') do |line|
|
35
|
+
report << "##{' ' * TAP_COMMENT_PAD + line}"
|
36
|
+
end
|
37
|
+
report << '#'
|
38
|
+
Array(test[:backtrace]).each do |line|
|
39
|
+
report << "##{' ' * TAP_COMMENT_PAD + line}"
|
40
|
+
end
|
41
|
+
report << ''
|
42
|
+
end
|
43
|
+
end
|
44
|
+
report.join("\n")
|
45
|
+
end
|
46
|
+
|
47
|
+
def report
|
48
|
+
puts(@data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'minitest'
|
2
2
|
require 'json'
|
3
|
-
require '
|
4
|
-
require '
|
3
|
+
require 'blinka_reporter/minitest_adapter'
|
4
|
+
require 'blinka_reporter/client'
|
5
5
|
|
6
6
|
module Minitest
|
7
7
|
def self.plugin_blinka_init(options)
|
@@ -11,114 +11,46 @@ module Minitest
|
|
11
11
|
def plugin_blinka_options(opts, options); end
|
12
12
|
|
13
13
|
module BlinkaPlugin
|
14
|
-
REPORT_PATH = 'blinka_results.json'.freeze
|
15
|
-
TAP_COMMENT_PAD = 8
|
16
14
|
class Reporter < Minitest::StatisticsReporter
|
17
15
|
attr_accessor :tests
|
18
16
|
|
19
|
-
def initialize(io = $stdout, options = {})
|
20
|
-
super
|
21
|
-
self.tests = []
|
22
|
-
end
|
23
|
-
|
24
17
|
def record(test)
|
25
18
|
super
|
19
|
+
self.tests ||= []
|
26
20
|
tests << test
|
27
21
|
end
|
28
22
|
|
29
23
|
def report
|
30
24
|
super
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
json_report(append: !ENV['BLINKA_APPEND'].nil?)
|
35
|
-
end
|
36
|
-
BlinkaClient.new.report if ENV['BLINKA_REPORT']
|
37
|
-
rescue BlinkaClient::BlinkaError => error
|
26
|
+
json_report
|
27
|
+
rescue BlinkaReporter::Error => error
|
38
28
|
puts(error)
|
39
29
|
end
|
40
30
|
|
41
31
|
private
|
42
32
|
|
43
|
-
def json_report
|
33
|
+
def json_report
|
34
|
+
report_path = ENV['BLINKA_PATH']
|
35
|
+
return if report_path.nil? || report_path.eql?('')
|
36
|
+
|
44
37
|
result = {
|
45
38
|
total_time: total_time,
|
46
39
|
nbr_tests: count,
|
47
40
|
nbr_assertions: assertions,
|
48
|
-
commit: find_commit,
|
49
|
-
tag: ENV.fetch('BLINKA_TAG', ''),
|
50
41
|
seed: options[:seed],
|
51
42
|
results:
|
52
|
-
tests.map
|
43
|
+
tests.map do |test_result|
|
44
|
+
BlinkaReporter::MinitestAdapter.new(test_result).report
|
45
|
+
end
|
53
46
|
}
|
54
|
-
result = append_previous(result) if append
|
55
47
|
|
56
|
-
File.open(
|
48
|
+
File.open(report_path, 'w+') do |file|
|
57
49
|
file.write(JSON.pretty_generate(result))
|
58
50
|
end
|
59
51
|
|
60
52
|
puts
|
61
|
-
puts("Test results written to `#{
|
62
|
-
end
|
63
|
-
|
64
|
-
# Based on https://github.com/kern/minitest-reporters/blob/master/lib/minitest/reporters/progress_reporter.rb
|
65
|
-
# Tries to adhere to https://testanything.org/tap-specification.html
|
66
|
-
def tap_report
|
67
|
-
puts
|
68
|
-
puts('TAP version 13')
|
69
|
-
puts("1..#{tests.length}")
|
70
|
-
tests.each_with_index do |test, index|
|
71
|
-
blinka = BlinkaMinitest.new(test)
|
72
|
-
test_str = "#{blinka.path} - #{test.name.tr('#', '_')}"
|
73
|
-
if test.passed?
|
74
|
-
puts "ok #{index + 1} - #{test_str}"
|
75
|
-
elsif test.skipped?
|
76
|
-
puts "ok #{index + 1} # skip: #{test_str}"
|
77
|
-
elsif test.failure
|
78
|
-
puts "not ok #{index + 1} - failed: #{test_str}"
|
79
|
-
blinka.message.each_line { |line| print_padded_comment(line) }
|
80
|
-
|
81
|
-
# test.failure.message.each_line { |line| print_padded_comment(line) }
|
82
|
-
unless test.failure.is_a?(MiniTest::UnexpectedError)
|
83
|
-
blinka.backtrace.each { |line| print_padded_comment(line) }
|
84
|
-
end
|
85
|
-
puts
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def print_padded_comment(line)
|
91
|
-
puts "##{' ' * TAP_COMMENT_PAD + line}"
|
92
|
-
end
|
93
|
-
|
94
|
-
def find_commit
|
95
|
-
ENV.fetch(
|
96
|
-
'BLINKA_COMMIT',
|
97
|
-
ENV.fetch(
|
98
|
-
'HEROKU_TEST_RUN_COMMIT_VERSION',
|
99
|
-
`git rev-parse HEAD`.chomp
|
100
|
-
)
|
101
|
-
)
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def parse_report
|
107
|
-
return unless File.exist?(REPORT_PATH)
|
108
|
-
JSON.parse(File.read(REPORT_PATH))
|
109
|
-
end
|
110
|
-
|
111
|
-
def append_previous(result)
|
112
|
-
previous = parse_report
|
113
|
-
return if previous.nil?
|
114
|
-
return if result[:commit] != previous['commit']
|
115
|
-
return if result[:tag] != previous['tag']
|
116
|
-
|
117
|
-
result[:total_time] += previous['total_time'] || 0
|
118
|
-
result[:nbr_tests] += previous['nbr_tests'] || 0
|
119
|
-
result[:nbr_assertions] += previous['nbr_assertions'] || 0
|
120
|
-
result[:results] += previous['results'] || []
|
121
|
-
result
|
53
|
+
puts("Test results written to `#{report_path}`")
|
122
54
|
end
|
123
55
|
end
|
124
56
|
end
|