diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3bd92c9..21cbfd6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-07-01 15:09:18 +0200 using RuboCop version 0.69.0. +# on 2019-08-12 11:40:23 +0300 using RuboCop version 0.71.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -13,14 +13,13 @@ Layout/EmptyLineAfterGuardClause: - 'lib/report_portal/cucumber/json_slurper.rb' - 'tests/features/step_definitions/steps.rb' -# Offense count: 5 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space, compact # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideHashLiteralBraces: Exclude: - - 'lib/reportportal.rb' - 'tests/formatter_spec.rb' # Offense count: 1 @@ -39,7 +38,7 @@ Lint/UriEscapeUnescape: Exclude: - 'lib/reportportal.rb' -# Offense count: 18 +# Offense count: 15 Metrics/AbcSize: Max: 106 @@ -58,7 +57,7 @@ Metrics/ClassLength: Metrics/CyclomaticComplexity: Max: 13 -# Offense count: 19 +# Offense count: 17 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 79 @@ -66,19 +65,12 @@ Metrics/MethodLength: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 162 + Max: 125 # Offense count: 5 Metrics/PerceivedComplexity: Max: 14 -# Offense count: 3 -# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db -Naming/UncommunicativeMethodParamName: - Exclude: - - 'lib/report_portal/patches/rest_client.rb' - # Offense count: 1 # Cop supports --auto-correct. Security/YAMLLoad: @@ -98,15 +90,7 @@ Style/CommentedKeyword: Exclude: - 'lib/report_portal/cucumber/report.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. -# SupportedStyles: assign_to_condition, assign_inside_condition -Style/ConditionalAssignment: - Exclude: - - 'lib/reportportal.rb' - -# Offense count: 9 +# Offense count: 7 Style/Documentation: Exclude: - 'spec/**/*' @@ -115,7 +99,6 @@ Style/Documentation: - 'lib/report_portal/cucumber/parallel_formatter.rb' - 'lib/report_portal/cucumber/parallel_report.rb' - 'lib/report_portal/logging/logger.rb' - - 'lib/report_portal/patches/rest_client.rb' - 'lib/report_portal/rspec/formatter.rb' - 'lib/report_portal/settings.rb' - 'lib/reportportal.rb' @@ -150,7 +133,7 @@ Style/FormatString: Style/FormatStringToken: EnforcedStyle: unannotated -# Offense count: 21 +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, never @@ -174,13 +157,12 @@ Style/GuardClause: - 'lib/reportportal.rb' - 'tests/features/step_definitions/steps.rb' -# Offense count: 3 +# Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys Style/HashSyntax: Exclude: - - 'lib/reportportal.rb' - 'tests/formatter_spec.rb' # Offense count: 5 @@ -191,14 +173,6 @@ Style/IfUnlessModifier: - 'lib/report_portal/cucumber/report.rb' - 'tests/features/support/env.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, Autocorrect. -# SupportedStyles: module_function, extend_self -Style/ModuleFunction: - Exclude: - - 'lib/report_portal/patches/rest_client.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -225,14 +199,6 @@ Style/PercentLiteralDelimiters: Exclude: - 'lib/report_portal/cucumber/json_slurper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'lib/reportportal.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist. @@ -254,14 +220,6 @@ Style/SignalException: - 'tests/features/support/env.rb' - 'tests/spec/test_spec.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: use_perl_names, use_english_names -Style/SpecialGlobalVars: - Exclude: - - 'lib/reportportal.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. @@ -295,7 +253,7 @@ Style/TrailingCommaInHashLiteral: Exclude: - 'lib/report_portal/settings.rb' -# Offense count: 104 +# Offense count: 99 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e4b3a..2d60d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,5 @@ ## In Git (not released) * reportportal/reportportal#293 - Re-licence client side to Apache 2.0 +* Remove retrying of all requests. Usually requests fail because of incorrect request parameters so retrying doesn't help and pollutes output. +* Remove multipart monkeypatch of RestClient. Replace RestClient with HTTP gem as it supports multipart fine diff --git a/lib/report_portal/patches/rest_client.rb b/lib/report_portal/patches/rest_client.rb deleted file mode 100644 index 0318c39..0000000 --- a/lib/report_portal/patches/rest_client.rb +++ /dev/null @@ -1,45 +0,0 @@ -# monkey-patch to temporarily solve the issue in https://github.com/rest-client/rest-client/pull/222 -module RestClient - module Payload - extend self - class Multipart < Base - def build_stream(params) - content_type = params.delete(:content_type) - b = '--' + boundary - - @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}") - @stream.binmode - @stream.write(b + EOL) - - case params - when Hash, ParamsArray - x = Utils.flatten_params(params) - else - x = params - end - - last_index = x.length - 1 - x.each_with_index do |a, index| - k, v = * a - if v.respond_to?(:read) && v.respond_to?(:path) - create_file_field(@stream, k, v) - else - create_regular_field(@stream, k, v, content_type) - end - @stream.write(EOL + b) - @stream.write(EOL) unless last_index == index - end - @stream.write('--') - @stream.write(EOL) - @stream.seek(0) - end - - def create_regular_field(s, k, v, type = nil) - s.write("Content-Disposition: form-data; name=\"#{k}\"") - s.write("#{EOL}Content-Type: #{type}#{EOL}") if type - s.write(EOL) - s.write(v) - end - end - end -end diff --git a/lib/reportportal.rb b/lib/reportportal.rb index a438c4a..dec89e5 100644 --- a/lib/reportportal.rb +++ b/lib/reportportal.rb @@ -1,27 +1,15 @@ require 'json' -require 'rest_client' +require 'http' require 'uri' require 'pathname' require 'tempfile' require_relative 'report_portal/settings' -require_relative 'report_portal/patches/rest_client' module ReportPortal TestItem = Struct.new(:name, :type, :id, :start_time, :description, :closed, :tags) LOG_LEVELS = { error: 'ERROR', warn: 'WARN', info: 'INFO', debug: 'DEBUG', trace: 'TRACE', fatal: 'FATAL', unknown: 'UNKNOWN' } - @response_handler = proc do |response, request, result, &block| - if (200..207).include? response.code - response - else - p "ReportPortal API returned #{response}" - p "Offending request method/URL: #{request.args[:method].upcase} #{request.args[:url]}" - p "Offending request payload: #{request.args[:payload]}}" - response.return!(request, result, &block) - end - end - class << self attr_accessor :launch_id, :current_scenario @@ -43,35 +31,26 @@ def status_to_level(status) end def start_launch(description, start_time = now) - url = "#{Settings.instance.project_url}/launch" data = { name: Settings.instance.launch, start_time: start_time, tags: Settings.instance.tags, description: description, mode: Settings.instance.launch_mode } - @launch_id = do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] - end + @launch_id = send_request(:post, 'launch', json: data)['id'] end def finish_launch(end_time = now) - url = "#{Settings.instance.project_url}/launch/#{@launch_id}/finish" data = { end_time: end_time } - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + send_request(:put, "launch/#{@launch_id}/finish", json: data) end def start_item(item_node) - url = "#{Settings.instance.project_url}/item" - url += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? + path = 'item' + path += "/#{item_node.parent.content.id}" unless item_node.parent && item_node.parent.is_root? item = item_node.content data = { start_time: item.start_time, name: item.name[0, 255], type: item.type.to_s, launch_id: @launch_id, description: item.description } data[:tags] = item.tags unless item.tags.empty? - do_request(url) do |resource| - JSON.parse(resource.post(data.to_json, content_type: :json, &@response_handler))['id'] - end + send_request(:post, path, json: data)['id'] end def finish_item(item, status = nil, end_time = nil, force_issue = nil) unless item.nil? || item.id.nil? || item.closed - url = "#{Settings.instance.project_url}/item/#{item.id}" data = { end_time: end_time.nil? ? now : end_time } data[:status] = status unless status.nil? if force_issue && status != :passed # TODO: check for :passed status is probably not needed @@ -79,9 +58,7 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) elsif status == :skipped data[:issue] = { issue_type: 'NOT_ISSUE' } end - do_request(url) do |resource| - resource.put data.to_json, content_type: :json, &@response_handler - end + send_request(:put, "item/#{item.id}", json: data) item.closed = true end end @@ -90,16 +67,12 @@ def finish_item(item, status = nil, end_time = nil, force_issue = nil) def send_log(status, message, time) unless @current_scenario.nil? || @current_scenario.closed # it can be nil if scenario outline in expand mode is executed - url = "#{Settings.instance.project_url}/log" data = { item_id: @current_scenario.id, time: time, level: status_to_level(status), message: message.to_s } - do_request(url) do |resource| - resource.post(data.to_json, content_type: :json, &@response_handler) - end + send_request(:post, 'log', json: data) end end def send_file(status, path, label = nil, time = now, mime_type = 'image/png') - url = "#{Settings.instance.project_url}/log" unless File.file?(path) if mime_type =~ /;base64$/ mime_type = mime_type[0..-8] @@ -113,49 +86,48 @@ def send_file(status, path, label = nil, time = now, mime_type = 'image/png') path = temp end File.open(File.realpath(path), 'rb') do |file| - label ||= File.basename(file) - json = { level: status_to_level(status), message: label, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } } - data = { :json_request_part => [json].to_json, label => file, :multipart => true, :content_type => 'application/json' } - do_request(url) do |resource| - resource.post(data, { content_type: 'multipart/form-data' }, &@response_handler) - end + filename = File.basename(file) + json = [{ level: status_to_level(status), message: label || filename, item_id: @current_scenario.id, time: time, file: { name: File.basename(file) } }] + form = { + json_request_part: HTTP::FormData::Part.new(JSON.dump(json), content_type: 'application/json'), + binary_part: HTTP::FormData::File.new(file, filename: filename) + } + send_request(:post, 'log', form: form) end end # needed for parallel formatter def item_id_of(name, parent_node) - if parent_node.is_root? # folder without parent folder - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" + path = if parent_node.is_root? # folder without parent folder + "item?filter.eq.launch=#{@launch_id}&filter.eq.name=#{URI.escape(name)}&filter.size.path=0" + else + "item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" + end + data = send_request(:get, path) + if data.key? 'content' + data['content'].empty? ? nil : data['content'][0]['id'] else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_node.content.id}&filter.eq.name=#{URI.escape(name)}" - end - do_request(url) do |resource| - data = JSON.parse(resource.get) - if data.key? 'content' - data['content'].empty? ? nil : data['content'][0]['id'] - else - nil # item isn't started yet - end + nil # item isn't started yet end end # needed for parallel formatter def close_child_items(parent_id) - if parent_id.nil? - url = "#{Settings.instance.project_url}/item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" - else - url = "#{Settings.instance.project_url}/item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" - end + path = if parent_id.nil? + "item?filter.eq.launch=#{@launch_id}&filter.size.path=0&page.page=1&page.size=100" + else + "item?filter.eq.parent=#{parent_id}&page.page=1&page.size=100" + end ids = [] loop do - response = do_request(url) { |r| JSON.parse(r.get) } - if response.key?('links') - link = response['links'].find { |i| i['rel'] == 'next' } + data = send_request(:get, path) + if data.key?('links') + link = data['links'].find { |i| i['rel'] == 'next' } url = link.nil? ? nil : link['href'] else url = nil end - response['content'].each do |i| + data['content'].each do |i| ids << i['id'] if i['has_childs'] && i['status'] == 'IN_PROGRESS' end break if url.nil? @@ -170,25 +142,13 @@ def close_child_items(parent_id) private - def create_resource(url) - props = { :headers => {:Authorization => "Bearer #{Settings.instance.uuid}"}} - verify_ssl = Settings.instance.disable_ssl_verification - props[:verify_ssl] = !verify_ssl unless verify_ssl.nil? - RestClient::Resource.new url, props - end - - def do_request(url) - resource = create_resource(url) - tries = 3 - begin - yield resource - rescue - p "Request to #{url} produced an exception: #{$!.class}: #{$!}" - $!.backtrace.each { |l| p l } - retry unless (tries -= 1).zero? - p "Failed to execute request to #{url} after 3 attempts." - nil + def send_request(verb, path, options = {}) + if Settings.instance.disable_ssl_verification + options[:ssl_context] = OpenSSL::SSL::SSLContext.new + options[:ssl_context].verify_mode = OpenSSL::SSL::VERIFY_NONE end + uri = "#{Settings.instance.project_url}/#{path}" + HTTP.auth("Bearer #{Settings.instance.uuid}").request(verb, uri, options).parse end end end diff --git a/reportportal.gemspec b/reportportal.gemspec index c5a110f..5668fa7 100644 --- a/reportportal.gemspec +++ b/reportportal.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.3.0' s.license = 'Apache-2.0' - s.add_dependency('rest-client', '~> 2.0') + s.add_dependency('http', '~> 4.0') s.add_dependency('rubytree', '>=0.9.3') s.add_development_dependency('rubocop', '0.71')