rpruby 1.2 → 1.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/.DS_Store +0 -0
- data/.github/workflows/main.yml +16 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/report_portal.yaml.example +12 -0
- data/lib/rpruby/cucumber/formatter.rb +87 -0
- data/lib/rpruby/cucumber/messagereport.rb +224 -0
- data/lib/rpruby/cucumber/parallel_formatter.rb +14 -0
- data/lib/rpruby/cucumber/parallel_report.rb +54 -0
- data/lib/rpruby/cucumber/report.rb +220 -0
- data/lib/rpruby/event_bus.rb +30 -0
- data/lib/rpruby/events/prepare_start_item_request.rb +13 -0
- data/lib/rpruby/http_client.rb +64 -0
- data/lib/rpruby/logging/log4r_outputter.rb +16 -0
- data/lib/rpruby/logging/logger.rb +36 -0
- data/lib/rpruby/logging/logging_appender.rb +18 -0
- data/lib/rpruby/models/item_search_options.rb +26 -0
- data/lib/rpruby/models/test_item.rb +22 -0
- data/lib/rpruby/rspec/formatter.rb +112 -0
- data/lib/rpruby/settings.rb +65 -0
- data/lib/rpruby/tasks.rb +27 -0
- data/lib/rpruby/version.rb +5 -0
- data/lib/rpruby.rb +223 -0
- data/rpruby.gemspec +44 -0
- metadata +38 -3
data/lib/rpruby/tasks.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'pathname'
|
3
|
+
require 'tempfile'
|
4
|
+
require_relative '../rpruby'
|
5
|
+
|
6
|
+
namespace :rpruby do
|
7
|
+
desc 'Start launch in Report Portal and print its id to $stdout (for use with attach_to_launch formatter mode)'
|
8
|
+
task :start_launch do
|
9
|
+
description = ENV['description'] || Rpruby::Settings.instance.description
|
10
|
+
file_to_write_launch_id = ENV['file_for_launch_id'] || Rpruby::Settings.instance.file_with_launch_id
|
11
|
+
file_to_write_launch_id ||= Pathname(Dir.tmpdir) + 'rp_launch_id.tmp'
|
12
|
+
launch_id = Rpruby.start_launch(description)
|
13
|
+
File.write(file_to_write_launch_id, launch_id)
|
14
|
+
puts launch_id
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Finish launch in Report Portal (for use with attach_to_launch formatter mode)'
|
18
|
+
task :finish_launch do
|
19
|
+
launch_id = ENV['launch_id'] || Rpruby::Settings.instance.launch_id
|
20
|
+
file_with_launch_id = ENV['file_with_launch_id'] || Rpruby::Settings.instance.file_with_launch_id
|
21
|
+
puts "Launch id isn't provided. Provide it either via RP_LAUNCH_ID or RP_FILE_WITH_LAUNCH_ID environment variables" if !launch_id && !file_with_launch_id
|
22
|
+
puts 'Both RP_LAUNCH_ID and RP_FILE_WITH_LAUNCH_ID are provided via environment variables' if launch_id && file_with_launch_id
|
23
|
+
Rpruby.launch_id = launch_id || File.read(file_with_launch_id)
|
24
|
+
Rpruby.close_child_items(nil)
|
25
|
+
Rpruby.finish_launch
|
26
|
+
end
|
27
|
+
end
|
data/lib/rpruby.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'base64'
|
3
|
+
require 'cgi'
|
4
|
+
require 'http'
|
5
|
+
require 'json'
|
6
|
+
require 'mime/types'
|
7
|
+
require 'pathname'
|
8
|
+
require 'tempfile'
|
9
|
+
require 'uri'
|
10
|
+
|
11
|
+
require_relative 'rpruby/event_bus'
|
12
|
+
require_relative 'rpruby/models/item_search_options'
|
13
|
+
require_relative 'rpruby/models/test_item'
|
14
|
+
require_relative 'rpruby/settings'
|
15
|
+
require_relative 'rpruby/http_client'
|
16
|
+
require_relative "rpruby/version"
|
17
|
+
|
18
|
+
module Rpruby
|
19
|
+
class Error < StandardError; end
|
20
|
+
LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' }.freeze
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_accessor :launch_id, :current_scenario
|
24
|
+
|
25
|
+
def now
|
26
|
+
(current_time.to_f * 1000).to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
def status_to_level(status)
|
30
|
+
case status
|
31
|
+
when :passed
|
32
|
+
LOG_LEVELS[:info]
|
33
|
+
when :failed, :undefined, :pending, :error
|
34
|
+
LOG_LEVELS[:error]
|
35
|
+
when :skipped
|
36
|
+
LOG_LEVELS[:warn]
|
37
|
+
else
|
38
|
+
LOG_LEVELS.fetch(status, LOG_LEVELS[:info])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def start_launch(description, start_time = now)
|
43
|
+
required_data = { name: Settings.instance.launch, start_time: start_time, description:
|
44
|
+
description, mode: Settings.instance.launch_mode }
|
45
|
+
data = prepare_options(required_data, Settings.instance)
|
46
|
+
@launch_id = send_request(:post, 'launch', json: data)['id']
|
47
|
+
end
|
48
|
+
|
49
|
+
def finish_launch(end_time = now)
|
50
|
+
data = { end_time: end_time }
|
51
|
+
send_request(:put, "launch/#{@launch_id}/finish", json: data)
|
52
|
+
end
|
53
|
+
|
54
|
+
def start_item(item_node)
|
55
|
+
path = 'item'
|
56
|
+
path += "/#{item_node.parent.content.id}" unless item_node.parent&.is_root?
|
57
|
+
item = item_node.content
|
58
|
+
data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description }
|
59
|
+
data[:tags] = item.tags unless item.tags.empty?
|
60
|
+
event_bus.broadcast(:prepare_start_item_request, request_data: data)
|
61
|
+
send_request(:post, path, json: data)['id']
|
62
|
+
end
|
63
|
+
|
64
|
+
def finish_item(item, status = nil, end_time = nil, force_issue = nil)
|
65
|
+
unless item.nil? || item.id.nil? || item.closed
|
66
|
+
data = { end_time: end_time.nil? ? now : end_time }
|
67
|
+
data[:status] = status unless status.nil?
|
68
|
+
if force_issue && status != :passed # TODO: check for :passed status is probably not needed
|
69
|
+
data[:issue] = { issue_type: 'AUTOMATION_BUG', comment: force_issue.to_s }
|
70
|
+
elsif status == :skipped
|
71
|
+
data[:issue] = { issue_type: 'NOT_ISSUE' }
|
72
|
+
end
|
73
|
+
send_request(:put, "item/#{item.id}", json: data)
|
74
|
+
item.closed = true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# TODO: implement force finish
|
79
|
+
|
80
|
+
def send_log(status, message, time)
|
81
|
+
unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed
|
82
|
+
data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s }
|
83
|
+
send_request(:post, 'log', json: data)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def send_file(status, path_or_src, label = nil, time = now, mime_type = 'image/png')
|
88
|
+
str_without_nils = path_or_src.to_s.gsub("\0", '') # file? does not allow NULLs inside the string
|
89
|
+
if File.file?(str_without_nils)
|
90
|
+
send_file_from_path(status, path_or_src, label, time, mime_type)
|
91
|
+
else
|
92
|
+
if mime_type =~ /;base64$/
|
93
|
+
mime_type = mime_type[0..-8]
|
94
|
+
path_or_src = Base64.decode64(path_or_src)
|
95
|
+
end
|
96
|
+
extension = ".#{MIME::Types[mime_type].first.extensions.first}"
|
97
|
+
Tempfile.open(['report_portal', extension]) do |tempfile|
|
98
|
+
tempfile.binmode
|
99
|
+
tempfile.write(path_or_src)
|
100
|
+
tempfile.rewind
|
101
|
+
send_file_from_path(status, tempfile.path, label, time, mime_type)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @option options [Hash] options, see ReportPortal::ItemSearchOptions
|
107
|
+
def get_items(filter_options = {})
|
108
|
+
page_size = 100
|
109
|
+
max_pages = 100
|
110
|
+
all_items = []
|
111
|
+
1.step.each do |page_number|
|
112
|
+
raise 'Too many pages with the results were returned' if page_number > max_pages
|
113
|
+
|
114
|
+
options = ItemSearchOptions.new({ page_size: page_size, page_number: page_number }.merge(filter_options))
|
115
|
+
page_items = send_request(:get, 'item', params: options.query_params)['content'].map do |item_params|
|
116
|
+
TestItem.new(item_params)
|
117
|
+
end
|
118
|
+
all_items += page_items
|
119
|
+
break if page_items.size < page_size
|
120
|
+
end
|
121
|
+
all_items
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param item_ids [Array<String>] an array of items to remove (represented by ids)
|
125
|
+
def delete_items(item_ids)
|
126
|
+
send_request(:delete, 'item', params: { ids: item_ids })
|
127
|
+
end
|
128
|
+
|
129
|
+
# needed for parallel formatter
|
130
|
+
def item_id_of(name, parent_node)
|
131
|
+
path = if parent_node.is_root? # folder without parent folder
|
132
|
+
"item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{CGI.escape(name)}&filter.size.path=0"
|
133
|
+
else
|
134
|
+
"item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{CGI.escape(name)}"
|
135
|
+
end
|
136
|
+
data = send_request(:get, path)
|
137
|
+
if data.key? 'content'
|
138
|
+
data['content'].empty? ? nil : data['content'][0]['id']
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# needed for parallel formatter
|
143
|
+
def launch_id_to_number()
|
144
|
+
path = "launch/#{@launch_id}"
|
145
|
+
data = send_request(:get, path)
|
146
|
+
data['id']
|
147
|
+
end
|
148
|
+
|
149
|
+
# needed for parallel formatter
|
150
|
+
def close_child_items(parent_id)
|
151
|
+
path = if parent_id.nil?
|
152
|
+
"item?filter.eq.launchId=#{launch_id_to_number}"
|
153
|
+
else
|
154
|
+
"item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100"
|
155
|
+
end
|
156
|
+
ids = []
|
157
|
+
loop do
|
158
|
+
data = send_request(:get, path)
|
159
|
+
if data.key?('links')
|
160
|
+
link = data['links'].find { |i| i['rel'] == 'next' }
|
161
|
+
url = link.nil? ? nil : link['href']
|
162
|
+
else
|
163
|
+
url = nil
|
164
|
+
end
|
165
|
+
data['content'].each do |i|
|
166
|
+
ids << i['id'] if i['has_childs'] && i['status'] == 'IN_PROGRESS'
|
167
|
+
end
|
168
|
+
break if url.nil?
|
169
|
+
end
|
170
|
+
|
171
|
+
ids.each do |id|
|
172
|
+
close_child_items(id)
|
173
|
+
finish_item(TestItem.new(id: id))
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Registers an event. The proc will be called back with the event object.
|
178
|
+
def on_event(name, &proc)
|
179
|
+
event_bus.on(name, &proc)
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def send_file_from_path(status, path, label, time, mime_type)
|
185
|
+
File.open(File.realpath(path), 'rb') do |file|
|
186
|
+
filename = File.basename(file)
|
187
|
+
json = [{ level: status_to_level(status), message: label || filename, item_id: @current_scenario.id, time: time, file: { name: filename } }]
|
188
|
+
form = {
|
189
|
+
json_request_part: HTTP::FormData::Part.new(JSON.dump(json), content_type: 'application/json'),
|
190
|
+
binary_part: HTTP::FormData::File.new(file, filename: filename, content_type: MIME::Types[mime_type].first.to_s)
|
191
|
+
}
|
192
|
+
send_request(:post, 'log', form: form)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def send_request(verb, path, options = {})
|
197
|
+
http_client.send_request(verb, path, options)
|
198
|
+
end
|
199
|
+
|
200
|
+
def http_client
|
201
|
+
@http_client ||= HttpClient.new
|
202
|
+
end
|
203
|
+
|
204
|
+
def current_time
|
205
|
+
# `now_without_mock_time` is provided by Timecop and returns a real, not mocked time
|
206
|
+
return Time.now_without_mock_time if Time.respond_to?(:now_without_mock_time)
|
207
|
+
|
208
|
+
Time.now
|
209
|
+
end
|
210
|
+
|
211
|
+
def event_bus
|
212
|
+
@event_bus ||= EventBus.new
|
213
|
+
end
|
214
|
+
|
215
|
+
def prepare_options(data, config = {})
|
216
|
+
if config.attributes
|
217
|
+
data[:attributes] = config.attributes
|
218
|
+
elsif (data[:tags] = config.tags)
|
219
|
+
end
|
220
|
+
data
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
data/rpruby.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# lib = File.expand_path('lib', __dir__)
|
3
|
+
# $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require_relative "lib/rpruby/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "rpruby"
|
9
|
+
spec.version = Rpruby::VERSION
|
10
|
+
spec.authors = ["Jayachandra Konduru(JC)"]
|
11
|
+
spec.email = ["jayak8390@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = 'ReportPortal Ruby Client'
|
14
|
+
spec.description = 'Cucumber and RSpec clients for EPAM ReportPortal system'
|
15
|
+
spec.homepage = 'https://github.com/jaya-konduru/rpruby'
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.required_ruby_version = ">= 2.4.0"
|
18
|
+
|
19
|
+
#spec.metadata["allowed_push_host"] = "TODO: Set to 'https://mygemserver.com'"
|
20
|
+
|
21
|
+
#spec.metadata["homepage_uri"] = spec.homepage
|
22
|
+
#spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
23
|
+
#spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
28
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
29
|
+
end
|
30
|
+
spec.bindir = "bin"
|
31
|
+
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
36
|
+
|
37
|
+
spec.add_dependency('http')
|
38
|
+
spec.add_dependency('mime-types')
|
39
|
+
spec.add_dependency('rubytree')
|
40
|
+
spec.add_development_dependency('rubocop')
|
41
|
+
|
42
|
+
# For more information and examples about making a new gem, checkout our
|
43
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
44
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rpruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jayachandra Konduru(JC)
|
@@ -69,10 +69,45 @@ dependencies:
|
|
69
69
|
description: Cucumber and RSpec clients for EPAM ReportPortal system
|
70
70
|
email:
|
71
71
|
- jayak8390@gmail.com
|
72
|
-
executables:
|
72
|
+
executables:
|
73
|
+
- console
|
74
|
+
- setup
|
73
75
|
extensions: []
|
74
76
|
extra_rdoc_files: []
|
75
|
-
files:
|
77
|
+
files:
|
78
|
+
- ".DS_Store"
|
79
|
+
- ".github/workflows/main.yml"
|
80
|
+
- ".gitignore"
|
81
|
+
- ".rspec"
|
82
|
+
- ".rubocop.yml"
|
83
|
+
- CHANGELOG.md
|
84
|
+
- CODE_OF_CONDUCT.md
|
85
|
+
- Gemfile
|
86
|
+
- LICENSE.txt
|
87
|
+
- README.md
|
88
|
+
- Rakefile
|
89
|
+
- bin/console
|
90
|
+
- bin/setup
|
91
|
+
- config/report_portal.yaml.example
|
92
|
+
- lib/rpruby.rb
|
93
|
+
- lib/rpruby/cucumber/formatter.rb
|
94
|
+
- lib/rpruby/cucumber/messagereport.rb
|
95
|
+
- lib/rpruby/cucumber/parallel_formatter.rb
|
96
|
+
- lib/rpruby/cucumber/parallel_report.rb
|
97
|
+
- lib/rpruby/cucumber/report.rb
|
98
|
+
- lib/rpruby/event_bus.rb
|
99
|
+
- lib/rpruby/events/prepare_start_item_request.rb
|
100
|
+
- lib/rpruby/http_client.rb
|
101
|
+
- lib/rpruby/logging/log4r_outputter.rb
|
102
|
+
- lib/rpruby/logging/logger.rb
|
103
|
+
- lib/rpruby/logging/logging_appender.rb
|
104
|
+
- lib/rpruby/models/item_search_options.rb
|
105
|
+
- lib/rpruby/models/test_item.rb
|
106
|
+
- lib/rpruby/rspec/formatter.rb
|
107
|
+
- lib/rpruby/settings.rb
|
108
|
+
- lib/rpruby/tasks.rb
|
109
|
+
- lib/rpruby/version.rb
|
110
|
+
- rpruby.gemspec
|
76
111
|
homepage: https://github.com/jaya-konduru/rpruby
|
77
112
|
licenses:
|
78
113
|
- MIT
|