Merge branch 'coverage-boost' into 'master'

Coverage boost

See merge request honeyryderchuck/httpx!123
This commit is contained in:
HoneyryderChuck 2021-02-21 16:19:16 +00:00
commit 2be907d1ed
26 changed files with 131 additions and 66 deletions

View File

@ -3,8 +3,10 @@ SimpleCov.start do
add_filter "/.bundle/"
add_filter "/vendor/"
add_filter "/test/"
add_filter "/integrations/"
add_filter "/lib/httpx/extensions.rb"
add_filter "/lib/httpx/loggable.rb"
add_filter "/lib/httpx/plugins/internal_telemetry.rb"
add_filter "/lib/httpx/plugins/multipart/mime_type_detector.rb"
add_filter "/lib/httpx/io/tls/"
add_filter "/lib/httpx/io/tls.rb"

View File

@ -123,11 +123,9 @@ module Faraday
end
def method_missing(meth, *args, &blk)
if @env && @env.respond_to?(meth)
@env.__send__(meth, *args, &blk)
else
super
end
return super unless @env && @env.respond_to?(meth)
@env.__send__(meth, *args, &blk)
end
end

View File

@ -117,7 +117,7 @@ module HTTPX
response = @request.response
log(level: 2) { "trailer headers received" }
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v.join(", ")}" }.join("\n") }
response.merge_headers(h)
end

View File

@ -46,7 +46,11 @@ module HTTPX
return :w if @streams.each_key.any? { |r| r.interests == :w }
return :r if @buffer.empty?
if @buffer.empty?
return if @streams.empty? && @pings.empty?
return :r
end
:rw
end
@ -333,11 +337,9 @@ module HTTPX
end
def method_missing(meth, *args, &blk)
if @connection.respond_to?(meth)
@connection.__send__(meth, *args, &blk)
else
super
end
return super unless @connection.respond_to?(meth)
@connection.__send__(meth, *args, &blk)
end
end
Connection.register "h2", Connection::HTTP2

View File

@ -17,8 +17,10 @@ module HTTPX
require "httpx/io/tls"
register "ssl", TLS
rescue LoadError
# :nocov:
require "httpx/io/ssl"
register "ssl", SSL
# :nocov:
end
else
require "httpx/io/ssl"

View File

@ -144,11 +144,7 @@ module HTTPX
def extra_options(options)
Class.new(options.class) do
def_option(:sigv4_signer) do |signer|
if signer.is_a?(Signer)
signer
else
Signer.new(signer)
end
signer.is_a?(Signer) ? signer : Signer.new(signer)
end
end.new.merge(options)
end

View File

@ -18,10 +18,7 @@ module HTTPX
module_function
def deflate(raw, buffer, chunk_size:)
deflater = Zlib::Deflate.new(Zlib::BEST_COMPRESSION,
Zlib::MAX_WBITS,
Zlib::MAX_MEM_LEVEL,
Zlib::HUFFMAN_ONLY)
deflater = Zlib::Deflate.new
while (chunk = raw.read(chunk_size))
compressed = deflater.deflate(chunk)
buffer << compressed

View File

@ -23,6 +23,7 @@ module HTTPX
end
def load_dependencies(*)
# :nocov:
begin
unless defined?(HTTP::FormData)
# in order not to break legacy code, we'll keep loading http/form_data for them.
@ -33,6 +34,7 @@ module HTTPX
end
rescue LoadError
end
# :nocov:
require "httpx/plugins/multipart/encoder"
require "httpx/plugins/multipart/part"
require "httpx/plugins/multipart/mime_type_detector"

View File

@ -43,7 +43,7 @@ module HTTPX
end
def __on_promise_request(parser, stream, h)
log(level: 1) do
log(level: 1, color: :yellow) do
# :nocov:
h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
# :nocov:
@ -57,6 +57,8 @@ module HTTPX
request.merge_headers(headers)
promise_headers[stream] = request
parser.pending.delete(request)
parser.streams[request] = stream
request.transition(:done)
else
stream.refuse
end
@ -67,7 +69,6 @@ module HTTPX
return unless request
parser.__send__(:on_stream_headers, stream, request, h)
request.transition(:done)
response = request.response
response.mark_as_pushed!
stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])

View File

@ -119,11 +119,9 @@ module HTTPX
end
def method_missing(meth, *args, &block)
if @options.response_class.public_method_defined?(meth)
response.__send__(meth, *args, &block)
else
super
end
return super unless @options.response_class.public_method_defined?(meth)
response.__send__(meth, *args, &block)
end
end
end

View File

@ -62,13 +62,7 @@ module HTTPX
handler = @registry.fetch(tag)
raise(Error, "#{tag} is not registered in #{self}") unless handler
case handler
when Symbol, String
obj = const_get(handler)
@registry[tag] = obj
else
handler
end
handler
end
# @param [Object] tag the identifier for the handler in the registry

View File

@ -223,11 +223,9 @@ module HTTPX
end
def method_missing(meth, *args, &block)
if @body.respond_to?(meth)
@body.__send__(meth, *args, &block)
else
super
end
return super unless @body.respond_to?(meth)
@body.__send__(meth, *args, &block)
end
end

View File

@ -55,20 +55,12 @@ module HTTPX
early_resolve(connection) || resolve(connection)
end
def timeout
@connections.map(&:timeout).min
end
def closed?
return true unless @resolver_connection
resolver_connection.closed?
true
end
def interests
return if @queries.empty?
resolver_connection.__send__(__method__)
def empty?
true
end
private

View File

@ -274,7 +274,8 @@ module HTTPX
end
else
def to_s
"#{@error.message} (#{@error.class})\n#{@error.backtrace.join("\n")}"
"#{@error.message} (#{@error.class})\n" \
"#{@error.backtrace.join("\n") if @error.backtrace}"
end
end

View File

@ -69,6 +69,11 @@ class HTTPX::Selector
if @selectables.empty?
@selectables = selectables
# do not run event loop if there's nothing to wait on.
# this might happen if connect failed and connection was unregistered.
return if (!r || r.empty?) && (!w || w.empty?)
break
else
@selectables = [*selectables, @selectables]

View File

@ -288,9 +288,11 @@ module HTTPX
::HTTPX.send(:const_set, :Session, proxy_session.class)
end
# :nocov:
if Session.default_options.debug_level > 2
proxy_session = plugin(:internal_telemetry)
::HTTPX.send(:remove_const, :Session)
::HTTPX.send(:const_set, :Session, proxy_session.class)
end
# :nocov:
end

View File

@ -44,11 +44,9 @@ module HTTPX::Transcoder
end
def method_missing(meth, *args, &block)
if @raw.respond_to?(meth)
@raw.__send__(meth, *args, &block)
else
super
end
return super unless @raw.respond_to?(meth)
@raw.__send__(meth, *args, &block)
end
end

View File

@ -31,7 +31,7 @@ module HTTPX
def on_headers: (Hash[String, Array[String]] headers) -> void
def on_trailers: (Array[String, String] headers) -> void
def on_trailers: (Hash[String, Array[String]] headers) -> void
def on_data: (string chunk) -> void

View File

@ -3,7 +3,7 @@ module HTTPX
include Callbacks
include Loggable
attr_reader streams: Hash[HTTP2Next::Stream, Response]
attr_reader streams: Hash[Request, HTTP2Next::Stream]
attr_reader pending: Array[Request]
@options: Options
@ -13,7 +13,7 @@ module HTTPX
@pings: Array[String]
@buffer: Buffer
def interests: () -> io_interests
def interests: () -> io_interests?
def close: () -> void

View File

@ -11,7 +11,7 @@ module HTTPX
module InstanceMethods
private
def promise_headers: () -> Hash[HTTP2Next::Stream, Request]
def promise_headers: () -> Hash[HTTP2Next::Stream, Request]
def __on_promise_request: (Connection::HTTP2, HTTP2Next::Stream, headers_input) -> void
def __on_promise_response: (Connection::HTTP2, HTTP2Next::Stream, headers_input) -> void
end

View File

@ -5,6 +5,17 @@ require_relative "test_helper"
class AltSvcTest < Minitest::Test
include HTTPX
def test_altsvc_cache
assert AltSvc.cached_altsvc("http://www.example.com").empty?
AltSvc.cached_altsvc_set("http://www.example.com", { "origin" => "http://alt.example.com", "ma" => 2 })
entries = AltSvc.cached_altsvc("http://www.example.com")
assert !entries.empty?
entry = entries.first
assert entry["origin"] == "http://alt.example.com"
sleep 3
assert AltSvc.cached_altsvc("http://www.example.com").empty?
end
def test_altsvc_parse_svc
assert [["h2=alt.example.com", {}]], AltSvc.parse("h2=alt.example.com").to_a
end

View File

@ -5,18 +5,18 @@ require_relative "test_helper"
class ErrorResponseTest < Minitest::Test
include HTTPX
def test_response_status
def test_error_response_status
r1 = ErrorResponse.new(request_mock, RuntimeError.new("wow"), {})
assert r1.status == "wow"
end
def test_response_raise_for_status
def test_error_response_raise_for_status
some_error = Class.new(RuntimeError)
r1 = ErrorResponse.new(request_mock, some_error.new("wow"), {})
assert_raises(some_error) { r1.raise_for_status }
end
def test_respond_method_missing_errors
def test_error_response_respond_method_missing_errors
r1 = ErrorResponse.new(request_mock, RuntimeError.new("wow"), {})
ex1 = assert_raises(NoMethodError) { r1.read }
assert ex1.message =~ /undefined response method/
@ -24,6 +24,12 @@ class ErrorResponseTest < Minitest::Test
assert ex2.message =~ /undefined method/
end
def test_error_response_to_s
r = ErrorResponse.new(request_mock, RuntimeError.new("wow"), {})
str = r.to_s
assert str.match(/wow \(.*RuntimeError.*\)/), "expected \"wow (RuntimeError)\" in \"#{str}\""
end
private
def request_mock

View File

@ -64,6 +64,24 @@ class HTTPTest < Minitest::Test
end
end
def test_trailers
server = HTTPTrailersServer.new
th = Thread.new { server.start }
begin
uri = "#{server.origin}/"
HTTPX.plugin(SessionWithPool).wrap do |http|
response = http.get(uri)
assert response.to_s == "trailers", "expected trailers endpoint"
verify_header(response.headers, "trailer", "x-trailer,x-trailer-2")
verify_header(response.headers, "x-trailer", "hello")
verify_header(response.headers, "x-trailer-2", "world")
end
ensure
server.shutdown
th.join
end
end
private
def origin(orig = httpbin)

View File

@ -47,7 +47,14 @@ class ResponseTest < Minitest::Test
body3 = Response::Body.new(Response.new(request("head"), 200, "2.0", {}), threshold_size: 1024)
assert body3.empty?, "body must be empty after initialization"
assert body3 == "", "HEAD requets body must be empty"
assert body3 == "", "HEAD request body must be empty (#{body3})"
text = +"heãd"
text.force_encoding(Encoding::BINARY)
body4 = Response::Body.new(Response.new(request, 200, "2.0", { "content-type" => "text/html; charset=utf" }), threshold_size: 1024)
body4.write(text)
req_text = body4.to_s
assert text == req_text, "request body must be in original encoding (#{req_text})"
end
def test_response_body_copy_to_memory

View File

@ -101,3 +101,37 @@ class NoContentLengthServer < TestServer
mount("/", NoContentLengthApp)
end
end
class HTTPTrailersServer < TestServer
module Trailers
def self.extended(obj)
super
obj.singleton_class.class_eval do
alias_method(:send_body_without_trailers, :send_body)
alias_method(:send_body, :send_body_with_trailers)
end
end
def send_body_with_trailers(socket)
send_body_without_trailers(socket)
socket.write(+"x-trailer: hello" << "\r\n")
socket.write(+"x-trailer-2: world" << "\r\n" << "\r\n")
end
end
class HTTPTrailersApp < WEBrick::HTTPServlet::AbstractServlet
def do_GET(_req, res) # rubocop:disable Naming/MethodName
res.status = 200
res["trailer"] = "x-trailer,x-trailer-2"
res.body = "trailers"
res.extend(Trailers)
end
end
def initialize(options = {})
super
mount("/", HTTPTrailersApp)
end
end

View File

@ -30,7 +30,8 @@ module Requests
# this google host will resolve to a CNAME
uri.host = "lh3.googleusercontent.com"
response = session.head(uri, resolver_class: resolver, resolver_options: options)
assert response.status < 500
assert !response.is_a?(HTTPX::ErrorResponse), "response was an error (#{response})"
assert response.status < 500, "unexpected HTTP error (#{response})"
response.close
end