Skip to content

Commit

Permalink
#10 more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
yegor256 committed Sep 11, 2019
1 parent 1b0fcaa commit ead2103
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 158 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ AllCops:
DisplayCopNames: true
TargetRubyVersion: 2.3.3

Style/ClassAndModuleChildren:
Enabled: false
Naming/FileName:
Enabled: false
Metrics/MethodLength:
Expand Down
8 changes: 8 additions & 0 deletions lib/random-port.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# The module for all classes.
#
# Author:: Yegor Bugayenko (yegor256@gmail.com)
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
# License:: MIT
module RandomPort
end

require 'random-port/pool'
193 changes: 96 additions & 97 deletions lib/random-port/pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,121 +24,120 @@

require 'socket'
require 'monitor'
require_relative '../random-port'

module RandomPort
# Pool of TPC ports.
#
# Use it like this:
#
# RandomPort::Pool.new.acquire do |port|
# # Use the TCP port. It will be returned back
# # to the pool afterwards.
# end
#
# You can specify the maximum amount of ports to acquire, using +limit+.
# If more acquiring requests will arrive, an exception will be raised.
#
# The class is thread-safe, by default. You can configure it to be
# not-thread-safe, using optional <tt>sync</tt> argument of the constructor.
#
# Author:: Yegor Bugayenko (yegor256@gmail.com)
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
# License:: MIT
class Pool
# If can't acquire by time out.
class Timeout < StandardError; end
# Pool of TPC ports.
#
# Use it like this:
#
# RandomPort::Pool.new.acquire do |port|
# # Use the TCP port. It will be returned back
# # to the pool afterwards.
# end
#
# You can specify the maximum amount of ports to acquire, using +limit+.
# If more acquiring requests will arrive, an exception will be raised.
#
# The class is thread-safe, by default. You can configure it to be
# not-thread-safe, using optional <tt>sync</tt> argument of the constructor.
#
# Author:: Yegor Bugayenko (yegor256@gmail.com)
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
# License:: MIT
class RandomPort::Pool
# If can't acquire by time out.
class Timeout < StandardError; end

attr_reader :limit
attr_reader :limit

# Ctor.
def initialize(sync: false, limit: 65_536)
@ports = []
@sync = sync
@monitor = Monitor.new
@limit = limit
end
# Ctor.
def initialize(sync: false, limit: 65_536)
@ports = []
@sync = sync
@monitor = Monitor.new
@limit = limit
end

# Application wide pool of ports
SINGLETON = Pool.new
# Application wide pool of ports
SINGLETON = RandomPort::Pool.new

# How many ports acquired now?
def count
@ports.count
end
alias size count
# How many ports acquired now?
def count
@ports.count
end
alias size count

# Is it empty?
def empty?
@ports.empty?
end
# Is it empty?
def empty?
@ports.empty?
end

# Acquire a new random TCP port.
#
# You can specify the number of ports to acquire. If it's more than one,
# an array will be returned.
#
# You can specify the amount of seconds to wait until a new port
# is available.
def acquire(total = 1, timeout: 4)
start = Time.now
loop do
if Time.now > start + timeout
raise Timeout, "Can't find a place in the pool of #{@limit} ports \
# Acquire a new random TCP port.
#
# You can specify the number of ports to acquire. If it's more than one,
# an array will be returned.
#
# You can specify the amount of seconds to wait until a new port
# is available.
def acquire(total = 1, timeout: 4)
start = Time.now
loop do
if Time.now > start + timeout
raise Timeout, "Can't find a place in the pool of #{@limit} ports \
for #{total} port(s), in #{format('%.02f', Time.now - start)}s"
end
opts = safe do
next if @ports.count + total > @limit
opts = Array.new(0, total)
begin
(0..(total - 1)).each do |i|
opts[i] = i.zero? ? take : take(opts[i - 1] + 1)
end
rescue Errno::EADDRINUSE, SocketError
next
end
next if opts.any? { |p| @ports.include?(p) }
d = total * (total - 1) / 2
next unless opts.inject(&:+) - total * opts.min == d
@ports += opts
opts
end
next if opts.nil?
opts = opts[0] if total == 1
return opts unless block_given?
end
opts = safe do
next if @ports.count + total > @limit
opts = Array.new(0, total)
begin
return yield opts
ensure
release(opts)
(0..(total - 1)).each do |i|
opts[i] = i.zero? ? take : take(opts[i - 1] + 1)
end
rescue Errno::EADDRINUSE, SocketError
next
end
next if opts.any? { |p| @ports.include?(p) }
d = total * (total - 1) / 2
next unless opts.inject(&:+) - total * opts.min == d
@ports += opts
opts
end
next if opts.nil?
opts = opts[0] if total == 1
return opts unless block_given?
begin
return yield opts
ensure
release(opts)
end
end
end

# Return it/them back to the pool.
def release(port)
safe do
if port.is_a?(Array)
port.each { |p| @ports.delete(p) }
else
@ports.delete(port)
end
# Return it/them back to the pool.
def release(port)
safe do
if port.is_a?(Array)
port.each { |p| @ports.delete(p) }
else
@ports.delete(port)
end
end
end

private
private

def take(opt = 0)
server = TCPServer.new('127.0.0.1', opt)
p = server.addr[1]
server.close
p
end
def take(opt = 0)
server = TCPServer.new('127.0.0.1', opt)
p = server.addr[1]
server.close
p
end

def safe
if @sync
@monitor.synchronize { yield }
else
yield
end
def safe
if @sync
@monitor.synchronize { yield }
else
yield
end
end
end
130 changes: 69 additions & 61 deletions test/test_pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,82 +29,90 @@
# Author:: Yegor Bugayenko (yegor256@gmail.com)
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
# License:: MIT
module RandomPort
class TestAmount < Minitest::Test
def test_acquires_and_releases
pool = Pool.new
port = pool.acquire
assert(!port.nil?)
assert(port.positive?)
pool.release(port)
end
class RandomPort::TestPool < Minitest::Test
def test_acquires_and_releases
pool = RandomPort::Pool.new
port = pool.acquire
server = TCPServer.new(port)
server.close
assert(!port.nil?)
assert(port.positive?)
pool.release(port)
end

def test_acquires_and_releases_three_ports
pool = Pool.new(limit: 3)
assert_equal(0, pool.size)
ports = pool.acquire(3, timeout: 16)
assert_equal(3, pool.size)
assert_equal(3, ports.count)
pool.release(ports)
assert_equal(0, pool.size)
def test_acquires_and_releases_three_ports
pool = RandomPort::Pool.new(limit: 3)
assert_equal(0, pool.size)
ports = pool.acquire(3, timeout: 16)
ports.each do |p|
server = TCPServer.new(p)
server.close
end
assert_equal(3, pool.size)
assert_equal(3, ports.count)
pool.release(ports)
assert_equal(0, pool.size)
end

def test_acquires_and_releases_three_ports_in_block
pool = Pool.new(limit: 3)
assert_equal(0, pool.size)
pool.acquire(3, timeout: 16) do |ports|
assert(ports.is_a?(Array))
assert_equal(3, ports.count)
assert_equal(3, pool.size)
def test_acquires_and_releases_three_ports_in_block
pool = RandomPort::Pool.new(limit: 3)
assert_equal(0, pool.size)
pool.acquire(3, timeout: 16) do |ports|
assert(ports.is_a?(Array))
assert_equal(3, ports.count)
assert_equal(3, pool.size)
ports.each do |p|
server = TCPServer.new(p)
server.close
end
assert_equal(0, pool.size)
end
assert_equal(0, pool.size)
end

def test_acquires_and_releases_in_block
result = Pool.new.acquire do |port|
assert(!port.nil?)
assert(port.positive?)
123
end
assert_equal(123, result)
def test_acquires_and_releases_in_block
result = RandomPort::Pool.new.acquire do |port|
assert(!port.nil?)
assert(port.positive?)
123
end
assert_equal(123, result)
end

def test_acquires_and_releases_safely
pool = Pool.new
assert_raises do
pool.acquire do
raise 'Itended'
end
def test_acquires_and_releases_safely
pool = RandomPort::Pool.new
assert_raises do
pool.acquire do
raise 'Itended'
end
assert(pool.count.zero?)
end
assert(pool.count.zero?)
end

def test_acquires_and_releases_from_singleton
Pool::SINGLETON.acquire do |port|
assert(!port.nil?)
assert(port.positive?)
end
def test_acquires_and_releases_from_singleton
RandomPort::Pool::SINGLETON.acquire do |port|
assert(!port.nil?)
assert(port.positive?)
end
end

def test_acquires_unique_numbers
total = 25
numbers = (0..total - 1).map { Pool::SINGLETON.acquire }
assert_equal(total, numbers.uniq.count)
end
def test_acquires_unique_numbers
total = 25
numbers = (0..total - 1).map { RandomPort::Pool::SINGLETON.acquire }
assert_equal(total, numbers.uniq.count)
end

def test_raises_when_too_many
pool = Pool.new(limit: 1)
pool.acquire
assert_raises Pool::Timeout do
pool.acquire(timeout: 0.1)
end
def test_raises_when_too_many
pool = RandomPort::Pool.new(limit: 1)
pool.acquire
assert_raises RandomPort::Pool::Timeout do
pool.acquire(timeout: 0.1)
end
end

def test_acquires_unique_numbers_in_no_sync_mode
total = 25
pool = Pool.new(sync: false)
numbers = (0..total - 1).map { pool.acquire }
assert_equal(total, numbers.uniq.count)
end
def test_acquires_unique_numbers_in_no_sync_mode
total = 25
pool = RandomPort::Pool.new(sync: false)
numbers = (0..total - 1).map { pool.acquire }
assert_equal(total, numbers.uniq.count)
end
end

0 comments on commit ead2103

Please sign in to comment.