From 31901c1b91463b0aae0af6f3ccda6205509d79bc Mon Sep 17 00:00:00 2001 From: Mike Blumtritt Date: Sat, 9 Mar 2024 15:45:19 +0000 Subject: [PATCH] v0.14.0 (#15) - optimise #read - optimise `Configuration#equal?` - reorganise tests - tidy-up --- .rspec | 1 + Rakefile | 17 +++--- lib/tcp-client.rb | 9 +--- lib/tcp-client/address.rb | 41 +++++--------- lib/tcp-client/configuration.rb | 53 +++++++------------ lib/tcp-client/deadline.rb | 12 ++--- lib/tcp-client/default_configuration.rb | 24 ++++----- lib/tcp-client/errors.rb | 36 ++++--------- lib/tcp-client/mixin/io_with_deadline.rb | 45 +++++----------- lib/tcp-client/ssl_socket.rb | 2 +- lib/tcp-client/version.rb | 2 +- spec/{ => lib}/tcp-client/address_spec.rb | 2 - .../tcp-client/configuration_spec.rb | 2 - .../tcp-client/default_configuration_spec.rb | 2 - spec/{ => lib}/tcp-client/version_spec.rb | 2 - .../tcp-client_spec.rb} | 5 +- stats.md | 1 - 17 files changed, 82 insertions(+), 174 deletions(-) create mode 100644 .rspec rename spec/{ => lib}/tcp-client/address_spec.rb (99%) rename spec/{ => lib}/tcp-client/configuration_spec.rb (99%) rename spec/{ => lib}/tcp-client/default_configuration_spec.rb (95%) rename spec/{ => lib}/tcp-client/version_spec.rb (85%) rename spec/{tcp_client_spec.rb => lib/tcp-client_spec.rb} (99%) diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..bf46e46 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require helper \ No newline at end of file diff --git a/Rakefile b/Rakefile index 0496225..1086557 100644 --- a/Rakefile +++ b/Rakefile @@ -2,21 +2,18 @@ $stdout.sync = $stderr.sync = true -require 'rake/clean' require 'bundler/gem_tasks' + require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:test) { _1.ruby_opts = %w[-w] } + require 'yard' +YARD::Rake::YardocTask.new(:doc) { _1.stats_options = %w[--list-undoc] } + +desc 'Run YARD development server' +task('doc:dev' => :clobber) { exec('yard server --reload') } CLEAN << '.yardoc' CLOBBER << 'doc' task(:default) { exec('rake --tasks') } - -RSpec::Core::RakeTask.new(:test) { |task| task.ruby_opts = %w[-w] } - -YARD::Rake::YardocTask.new(:doc) do |task| - task.stats_options = %w[--list-undoc] -end - -desc 'Run YARD development server' -task('doc:dev' => :clobber) { exec('yard server --reload') } diff --git a/lib/tcp-client.rb b/lib/tcp-client.rb index f559d45..de2a100 100644 --- a/lib/tcp-client.rb +++ b/lib/tcp-client.rb @@ -92,7 +92,6 @@ def self.open(address, configuration = nil) # @see #with_deadline # def self.with_deadline(timeout, address, configuration = nil) - client = nil raise(NoBlockGivenError) unless block_given? client = new client.with_deadline(timeout) do @@ -116,9 +115,7 @@ def self.with_deadline(timeout, address, configuration = nil) # @attribute [r] :closed? # @return [Boolean] whether the connection is closed # - def closed? - @socket.nil? || @socket.closed? - end + def closed? = @socket.nil? || @socket.closed? # # Close the current connection if connected. @@ -240,9 +237,7 @@ def readline(separator = $/, chomp: false, timeout: nil, exception: nil) # # @see Address#to_s # - def to_s - @address&.to_s || '' - end + def to_s = @address.to_s # # Executes a block with a given overall time limit. diff --git a/lib/tcp-client/address.rb b/lib/tcp-client/address.rb index 6ca5203..24fc314 100644 --- a/lib/tcp-client/address.rb +++ b/lib/tcp-client/address.rb @@ -19,7 +19,7 @@ class Address # @return [Addrinfo] the address info # def addrinfo - freeze if @addrinfo.nil? + freeze unless @addrinfo @addrinfo end @@ -28,7 +28,7 @@ def addrinfo # @return [String] the host name # def host - freeze if @host.nil? + freeze unless @host @host end alias hostname host @@ -37,9 +37,7 @@ def host # @attribute [r] port # @return [Integer] the port number # - def port - addrinfo.ip_port - end + def port = addrinfo.ip_port # # Initializes an address @@ -73,18 +71,14 @@ def port # # @param port [Integer] the addressed port # - def initialize(addr) - @addr = addr - end + def initialize(addr) = (@addr = addr) # # Convert `self` to a Hash containing host and port attribute. # # @return [Hash] host and port # - def to_hash - { host: host, port: port } - end + def to_hash = { host: host, port: port } # # Convert `self` to a Hash containing host and port attribute. @@ -93,16 +87,12 @@ def to_hash # @overload to_h(&block) # @return [Hash] host and port # - def to_h(&block) - block ? to_hash.to_h(&block) : to_hash - end + def to_h(&block) = block ? to_hash.to_h(&block) : to_hash # # @return [String] text representation of self as "host:port" # - def to_s - host.index(':') ? "[#{host}]:#{port}" : "#{host}:#{port}" - end + def to_s = host.index(':') ? "[#{host}]:#{port}" : "#{host}:#{port}" # # Force the address resolution and prevents further modifications of itself. @@ -114,20 +104,15 @@ def freeze solve @addrinfo.freeze @host.freeze - @addr = nil super end # @!visibility private - def ==(other) - to_hash == other.to_h - end + def ==(other) = to_hash == other.to_h alias eql? == # @!visibility private - def equal?(other) - self.class == other.class && self == other - end + def equal?(other) = self.class == other.class && self == other private @@ -142,6 +127,7 @@ def solve else from_string(@addr) end + @addr = nil end def from_self_class(address) @@ -154,14 +140,13 @@ def from_self_class(address) end def from_addrinfo(addrinfo) - @host = addrinfo.getnameinfo(Socket::NI_NUMERICSERV).first - @addrinfo = addrinfo + @host = (@addrinfo = addrinfo).getnameinfo(Socket::NI_NUMERICSERV).first end def from_string(str) @host, port = host_n_port(str.to_s) - return @addrinfo = Addrinfo.tcp(@host, port) if @host - from_addrinfo(Addrinfo.tcp(nil, port)) + @addrinfo = Addrinfo.tcp(@host, port) + @host ||= @addrinfo.getnameinfo(Socket::NI_NUMERICSERV).first end def host_n_port(str) diff --git a/lib/tcp-client/configuration.rb b/lib/tcp-client/configuration.rb index df17361..1ec854b 100644 --- a/lib/tcp-client/configuration.rb +++ b/lib/tcp-client/configuration.rb @@ -93,9 +93,7 @@ def reverse_lookup=(value) # @!parse attr_reader :ssl? # @return [Boolean] whether SSL is configured, see {#ssl_params} # - def ssl? - @ssl_params ? true : false - end + def ssl? = @ssl_params ? true : false # # Parameters used to initialize a SSL context. SSL/TLS will only be used if @@ -111,7 +109,7 @@ def ssl_params=(value) if value.respond_to?(:to_hash) Hash[value.to_hash] elsif value.respond_to?(:to_h) - value.nil? ? nil : Hash[value.to_h] + value.nil? ? nil : value.to_h.dup else value ? {} : nil end @@ -133,7 +131,7 @@ def ssl_params=(value) attr_reader :connect_timeout def connect_timeout=(value) - @connect_timeout = seconds(value) + @connect_timeout = as_seconds(value) end # @@ -146,8 +144,7 @@ def connect_timeout=(value) attr_reader :connect_timeout_error def connect_timeout_error=(value) - raise(NotAnExceptionError, value) unless exception_class?(value) - @connect_timeout_error = value + @connect_timeout_error = as_exception(value) end # @@ -161,7 +158,7 @@ def connect_timeout_error=(value) attr_reader :read_timeout def read_timeout=(value) - @read_timeout = seconds(value) + @read_timeout = as_seconds(value) end # @@ -174,8 +171,7 @@ def read_timeout=(value) attr_reader :read_timeout_error def read_timeout_error=(value) - raise(NotAnExceptionError, value) unless exception_class?(value) - @read_timeout_error = value + @read_timeout_error = as_exception(value) end # @@ -189,7 +185,7 @@ def read_timeout_error=(value) attr_reader :write_timeout def write_timeout=(value) - @write_timeout = seconds(value) + @write_timeout = as_seconds(value) end # @@ -202,8 +198,7 @@ def write_timeout=(value) attr_reader :write_timeout_error def write_timeout_error=(value) - raise(NotAnExceptionError, value) unless exception_class?(value) - @write_timeout_error = value + @write_timeout_error = as_exception(value) end # @@ -218,7 +213,7 @@ def write_timeout_error=(value) # @see #write_timeout # def timeout=(value) - @connect_timeout = @write_timeout = @read_timeout = seconds(value) + @connect_timeout = @write_timeout = @read_timeout = as_seconds(value) end # @@ -235,9 +230,8 @@ def timeout=(value) # @see #write_timeout_error # def timeout_error=(value) - raise(NotAnExceptionError, value) unless exception_class?(value) @connect_timeout_error = - @read_timeout_error = @write_timeout_error = value + @read_timeout_error = @write_timeout_error = as_exception(value) end # @!endgroup @@ -291,9 +285,7 @@ def to_hash # # @see #configure # - def to_h(&block) - block ? to_hash.to_h(&block) : to_hash - end + def to_h(&block) = block ? to_hash.to_h(&block) : to_hash # # Configures the instance with given options Hash. @@ -318,7 +310,7 @@ def to_h(&block) # @return [Configuration] self # def configure(options) - options.each_pair { |attribute, value| set(attribute, value) } + options.each_pair { set(*_1) } self end @@ -336,30 +328,25 @@ def initialize_copy(_org) end # @!visibility private - def ==(other) - to_hash == other.to_h - end + def ==(other) = to_hash == other.to_h alias eql? == # @!visibility private - def equal?(other) - self.class == other.class && self == other - end + def equal?(other) = self.class == other.class && to_hash == other.to_hash private - def exception_class?(value) - value.is_a?(Class) && value < Exception + def as_seconds(value) = value&.to_f&.positive? ? value : nil + + def as_exception(value) + return value if value.is_a?(Class) && value < Exception + raise(NotAnExceptionError, value, caller(2)) end def set(attribute, value) public_send("#{attribute}=", value) rescue NoMethodError - raise(UnknownAttributeError, attribute) - end - - def seconds(value) - value&.to_f&.positive? ? value : nil + raise(UnknownAttributeError, attribute, caller(2)) end end end diff --git a/lib/tcp-client/deadline.rb b/lib/tcp-client/deadline.rb index a500dba..2423653 100644 --- a/lib/tcp-client/deadline.rb +++ b/lib/tcp-client/deadline.rb @@ -7,9 +7,7 @@ def initialize(timeout) @deadline = timeout&.positive? ? now + timeout : nil end - def valid? - !@deadline.nil? - end + def valid? = !@deadline.nil? def remaining_time @deadline && (remaining = @deadline - now) > 0 ? remaining : nil @@ -18,13 +16,9 @@ def remaining_time private if defined?(Process::CLOCK_MONOTONIC) - def now - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end + def now = Process.clock_gettime(Process::CLOCK_MONOTONIC) else - def now - ::Time.now - end + def now = ::Time.now end end diff --git a/lib/tcp-client/default_configuration.rb b/lib/tcp-client/default_configuration.rb index 30ace62..a0696e5 100644 --- a/lib/tcp-client/default_configuration.rb +++ b/lib/tcp-client/default_configuration.rb @@ -37,19 +37,15 @@ def configure(options = nil, &block) end class Configuration - class << self - # - # @attribute [r] :default - # @return [Configuration] used by default if no dedicated configuration - # was specified - # - # @see TCPClient.open - # @see TCPClient.with_deadline - # @see TCPClient#connect - # - def default - TCPClient.default_configuration - end - end + # + # @attribute [r] :default + # @return [Configuration] used by default if no dedicated configuration + # was specified + # + # @see TCPClient.open + # @see TCPClient.with_deadline + # @see TCPClient#connect + # + def self.default = TCPClient.default_configuration end end diff --git a/lib/tcp-client/errors.rb b/lib/tcp-client/errors.rb index 8f99632..fe3346e 100644 --- a/lib/tcp-client/errors.rb +++ b/lib/tcp-client/errors.rb @@ -6,9 +6,7 @@ class TCPClient # not available. # class NoOpenSSLError < RuntimeError - def initialize - super('OpenSSL is not available') - end + def initialize = super('OpenSSL is not available') end # @@ -16,9 +14,7 @@ def initialize # specified. # class NoBlockGivenError < ArgumentError - def initialize - super('no block given') - end + def initialize = super('no block given') end # @@ -28,9 +24,7 @@ class InvalidDeadLineError < ArgumentError # # @param timeout [Object] the invalid value # - def initialize(timeout) - super("invalid deadline - #{timeout}") - end + def initialize(timeout) = super("invalid deadline - #{timeout}") end # @@ -40,9 +34,7 @@ class UnknownAttributeError < ArgumentError # # @param attribute [Object] the undefined attribute # - def initialize(attribute) - super("unknown attribute - #{attribute}") - end + def initialize(attribute) = super("unknown attribute - #{attribute}") end # @@ -74,9 +66,7 @@ class NetworkError < StandardError # but is not connected. # class NotConnectedError < NetworkError - def initialize - super('client not connected') - end + def initialize = super('client not connected') end # @@ -104,9 +94,7 @@ def initialize(message = nil) # @attribute [r] action # @return [Symbol] the action which timed out # - def action - :process - end + def action = :process end # @@ -117,9 +105,7 @@ class ConnectTimeoutError < TimeoutError # @attribute [r] action # @return [Symbol] the action which timed out: `:connect` # - def action - :connect - end + def action = :connect end # @@ -130,9 +116,7 @@ class ReadTimeoutError < TimeoutError # @attribute [r] action # @return [Symbol] the action which timed out: :read` # - def action - :read - end + def action = :read end # @@ -143,9 +127,7 @@ class WriteTimeoutError < TimeoutError # @attribute [r] action # @return [Symbol] the action which timed out: `:write` # - def action - :write - end + def action = :write end NoOpenSSL = NoOpenSSLError # @!visibility private diff --git a/lib/tcp-client/mixin/io_with_deadline.rb b/lib/tcp-client/mixin/io_with_deadline.rb index 0795b8c..3c32466 100644 --- a/lib/tcp-client/mixin/io_with_deadline.rb +++ b/lib/tcp-client/mixin/io_with_deadline.rb @@ -6,25 +6,18 @@ class << self private def included(mod) - return if supports_wait?(mod) - mod.include(mod.method_defined?(:to_io) ? WaitWithIO : WaitWithSelect) - end - - def supports_wait?(mod) - mod.method_defined?(:wait_writable) && - mod.method_defined?(:wait_readable) + return if defined?(mod.wait_writable) && defined?(mod.wait_readable) + mod.include(defined?(mod.to_io) ? WaitWithIO : WaitWithSelect) end end def read_with_deadline(nbytes, deadline, exception) raise(exception) unless deadline.remaining_time return fetch_avail(deadline, exception) if nbytes.nil? - return ''.b if nbytes.zero? @read_buffer ||= ''.b while @read_buffer.bytesize < nbytes - read = fetch_next(deadline, exception) and next @read_buffer << read - close - break + read = fetch_next(deadline, exception) + read ? @read_buffer << read : (break close) end fetch_slice(nbytes) end @@ -32,12 +25,10 @@ def read_with_deadline(nbytes, deadline, exception) def read_to_with_deadline(sep, deadline, exception) raise(exception) unless deadline.remaining_time @read_buffer ||= ''.b - while @read_buffer.index(sep).nil? - read = fetch_next(deadline, exception) and next @read_buffer << read - close - break + while (index = @read_buffer.index(sep)).nil? + read = fetch_next(deadline, exception) + read ? @read_buffer << read : (break close) end - index = @read_buffer.index(sep) return fetch_slice(index + sep.bytesize) if index result = @read_buffer @read_buffer = nil @@ -53,7 +44,7 @@ def write_with_deadline(data, deadline, exception) with_deadline(deadline, exception) do write_nonblock(data, exception: false) end - (result += written) >= size and return result + return result if (result += written) >= size data = data.byteslice(written, data.bytesize - written) end end @@ -70,7 +61,7 @@ def fetch_avail(deadline, exception) end def fetch_slice(size) - return ''.b if size.zero? + return ''.b if size <= 0 result = @read_buffer.byteslice(0, size) rest = @read_buffer.bytesize - result.bytesize @read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest) @@ -101,23 +92,13 @@ def with_deadline(deadline, exception) end module WaitWithIO - def wait_writable(remaining_time) - to_io.wait_writable(remaining_time) - end - - def wait_readable(remaining_time) - to_io.wait_readable(remaining_time) - end + def wait_writable(time) = to_io.wait_writable(time) + def wait_readable(time) = to_io.wait_readable(time) end module WaitWithSelect - def wait_writable(remaining_time) - ::IO.select(nil, [self], nil, remaining_time) - end - - def wait_readable(remaining_time) - ::IO.select([self], nil, nil, remaining_time) - end + def wait_writable(time) = ::IO.select(nil, [self], nil, time) + def wait_readable(time) = ::IO.select([self], nil, nil, time) end private_constant(:WaitWithIO, :WaitWithSelect) diff --git a/lib/tcp-client/ssl_socket.rb b/lib/tcp-client/ssl_socket.rb index 23020d7..11209bc 100644 --- a/lib/tcp-client/ssl_socket.rb +++ b/lib/tcp-client/ssl_socket.rb @@ -30,7 +30,7 @@ def create_context(ssl_params) ::OpenSSL::SSL::SSLContext.new.tap do |ctx| ctx.set_params(ssl_params) ctx.session_cache_mode = CONTEXT_CACHE_MODE - ctx.session_new_cb = proc { |_, session| @new_session = session } + ctx.session_new_cb = proc { @new_session = _2 } end end diff --git a/lib/tcp-client/version.rb b/lib/tcp-client/version.rb index ef42538..99a7e16 100644 --- a/lib/tcp-client/version.rb +++ b/lib/tcp-client/version.rb @@ -2,5 +2,5 @@ class TCPClient # The current version number. - VERSION = '0.13.0' + VERSION = '0.14.0' end diff --git a/spec/tcp-client/address_spec.rb b/spec/lib/tcp-client/address_spec.rb similarity index 99% rename from spec/tcp-client/address_spec.rb rename to spec/lib/tcp-client/address_spec.rb index 348861b..4229726 100644 --- a/spec/tcp-client/address_spec.rb +++ b/spec/lib/tcp-client/address_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../helper' - RSpec.describe TCPClient::Address do subject(:address) { TCPClient::Address.new(argument) } diff --git a/spec/tcp-client/configuration_spec.rb b/spec/lib/tcp-client/configuration_spec.rb similarity index 99% rename from spec/tcp-client/configuration_spec.rb rename to spec/lib/tcp-client/configuration_spec.rb index 16aa2b9..775c9bb 100644 --- a/spec/tcp-client/configuration_spec.rb +++ b/spec/lib/tcp-client/configuration_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../helper' - RSpec.describe TCPClient::Configuration do describe '.create' do it 'yields a configuration' do diff --git a/spec/tcp-client/default_configuration_spec.rb b/spec/lib/tcp-client/default_configuration_spec.rb similarity index 95% rename from spec/tcp-client/default_configuration_spec.rb rename to spec/lib/tcp-client/default_configuration_spec.rb index 26c0877..1f15617 100644 --- a/spec/tcp-client/default_configuration_spec.rb +++ b/spec/lib/tcp-client/default_configuration_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../helper' - RSpec.describe 'TCPClient.configure' do it 'is the default configuration' do expect(TCPClient.configure).to be TCPClient::Configuration.default diff --git a/spec/tcp-client/version_spec.rb b/spec/lib/tcp-client/version_spec.rb similarity index 85% rename from spec/tcp-client/version_spec.rb rename to spec/lib/tcp-client/version_spec.rb index ddafa97..82fe03c 100644 --- a/spec/tcp-client/version_spec.rb +++ b/spec/lib/tcp-client/version_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../helper' - RSpec.describe TCPClient::VERSION do it { is_expected.to match(/\A[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\z/) } it { is_expected.to be_frozen } diff --git a/spec/tcp_client_spec.rb b/spec/lib/tcp-client_spec.rb similarity index 99% rename from spec/tcp_client_spec.rb rename to spec/lib/tcp-client_spec.rb index 552d0ee..8295413 100644 --- a/spec/tcp_client_spec.rb +++ b/spec/lib/tcp-client_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'helper' - RSpec.describe TCPClient do subject(:client) { TCPClient.new.connect('localhost:1234', configuration) } let(:configuration) do @@ -162,7 +160,8 @@ it 'is not closed' do allow_any_instance_of(Socket).to receive(:connect_nonblock).with( - kind_of(String), exception: false + kind_of(String), + exception: false ) client.connect('localhost:1234', configuration, timeout: 10) expect(client).not_to be_closed diff --git a/stats.md b/stats.md index 77565b0..28c815b 100644 --- a/stats.md +++ b/stats.md @@ -16,7 +16,6 @@ ![last commit](https://img.shields.io/github/last-commit/mblumtritt/tcp-client/main) ![files](https://img.shields.io/github/directory-file-count/mblumtritt/tcp-client) -![build status](https://img.shields.io/github/workflow/status/mblumtritt/tcp-client/Test) ![dependencies](https://img.shields.io/librariesio/github/mblumtritt/tcp-client) ![commit activity](https://img.shields.io/github/commit-activity/m/mblumtritt/tcp-client)