random-port 0.7.3 → 0.7.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 627f7f283d6d1958380c9794dd78a50b7068144afa5e7b6f988d8cca325cf114
4
- data.tar.gz: 4559900eed96735698944d63366666130fbb729b1a12a26d79a9e77eb5085bec
3
+ metadata.gz: 685cbbb964686d55837c88f3856b60207d3abaadb76e96ba9f9093bc8aba0a9c
4
+ data.tar.gz: 83e7ea6c0ac635b3c06cb1d190d3a991be61d4d534596ae10852c1a7a8df542d
5
5
  SHA512:
6
- metadata.gz: e1bea15f564c1b5f9e4bc292b71501aa799b6b674337b1c9c504cbd7edee6bbd508c4beb4fe3648fdc83a184d3924ca4a595b253b383f9d2cb7d213ee518ae1c
7
- data.tar.gz: db5b1ee1490292eaf644eb68567d1289354a355c6a2c768037005db2d44111bc232f120358ffc231964579b005315269e0927de96f547c4ad012a87c84220b65
6
+ metadata.gz: 48adf68a56c6ac4550287538b7cefa09cf033575e56dd8cdd6d53f0bc49004c87270f4c3b1d5c30576e721bf9c0589245c50854b494a3ec191588fc9e974253a
7
+ data.tar.gz: bd33bb2321ea165a1e9e546fa257db2a824572a07462af36ebd0e4e2fe93a4679c16f269170f51042046e139c7ed35a76bd4bd39338dd6a00777ddb4c1fb1214
data/.0pdd.yml CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -19,12 +19,13 @@
19
19
  # SOFTWARE.
20
20
  ---
21
21
  name: codecov
22
- on:
22
+ 'on':
23
23
  push:
24
24
  branches:
25
25
  - master
26
26
  jobs:
27
27
  codecov:
28
+ timeout-minutes: 5
28
29
  runs-on: ubuntu-24.04
29
30
  steps:
30
31
  - uses: actions/checkout@v4
@@ -36,3 +37,5 @@ jobs:
36
37
  - uses: codecov/codecov-action@v5
37
38
  with:
38
39
  token: ${{ secrets.CODECOV_TOKEN }}
40
+ files: coverage/.resultset.json
41
+ fail_ci_if_error: true
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -21,7 +21,11 @@
21
21
  name: copyrights
22
22
  'on':
23
23
  push:
24
+ branches:
25
+ - master
24
26
  pull_request:
27
+ branches:
28
+ - master
25
29
  jobs:
26
30
  copyrights:
27
31
  runs-on: ubuntu-24.04
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -19,7 +19,7 @@
19
19
  # SOFTWARE.
20
20
  ---
21
21
  name: pdd
22
- on:
22
+ 'on':
23
23
  push:
24
24
  branches:
25
25
  - master
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -19,7 +19,7 @@
19
19
  # SOFTWARE.
20
20
  ---
21
21
  name: rake
22
- on:
22
+ 'on':
23
23
  push:
24
24
  branches:
25
25
  - master
@@ -27,8 +27,8 @@ on:
27
27
  branches:
28
28
  - master
29
29
  jobs:
30
- test:
31
- name: test
30
+ rake:
31
+ timeout-minutes: 5
32
32
  strategy:
33
33
  matrix:
34
34
  os: [ubuntu-24.04, macos-15, windows-2022]
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -19,9 +19,13 @@
19
19
  # SOFTWARE.
20
20
  ---
21
21
  name: xcop
22
- on:
22
+ 'on':
23
23
  push:
24
+ branches:
25
+ - master
24
26
  pull_request:
27
+ branches:
28
+ - master
25
29
  jobs:
26
30
  xcop:
27
31
  runs-on: ubuntu-24.04
data/.gitignore CHANGED
@@ -5,4 +5,4 @@ Gemfile.lock
5
5
  rdoc/
6
6
  coverage/
7
7
  .yardoc/
8
- doc/
8
+ doc/
data/.rubocop.yml CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -23,7 +23,11 @@ AllCops:
23
23
  TargetRubyVersion: 2.3.3
24
24
  NewCops: enable
25
25
  SuggestExtensions: false
26
+ require:
27
+ - rubocop-minitest
26
28
 
29
+ Minitest/EmptyLineBeforeAssertionMethods:
30
+ Enabled: false
27
31
  Layout/EmptyLineAfterGuardClause:
28
32
  Enabled: false
29
33
  Style/ClassAndModuleChildren:
@@ -44,3 +48,5 @@ Metrics/CyclomaticComplexity:
44
48
  Max: 20
45
49
  Metrics/AbcSize:
46
50
  Max: 60
51
+ Metrics/ClassLength:
52
+ Max: 150
data/.rultor.yml CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2024 Yegor Bugayenko
1
+ # Copyright (c) 2018-2025 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2024 Yegor Bugayenko
3
+ # Copyright (c) 2018-2025 Yegor Bugayenko
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the 'Software'), to deal
@@ -24,9 +24,11 @@ source 'https://rubygems.org'
24
24
  gemspec
25
25
 
26
26
  gem 'minitest', '5.25.4', require: false
27
+ gem 'qbash', '>=0.2.2', require: false
27
28
  gem 'rake', '13.2.1', require: false
28
- gem 'rubocop', '1.69.2', require: false
29
- gem 'rubocop-rspec', '3.3.0', require: false
29
+ gem 'rubocop', '1.71.0', require: false
30
+ gem 'rubocop-minitest', '0.36.0', require: false
31
+ gem 'rubocop-rspec', '3.4.0', require: false
30
32
  gem 'simplecov', '0.22.0', require: false
31
33
  gem 'threads', '0.4.1', require: false
32
34
  gem 'yard', '0.9.37', require: false
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  (The MIT License)
2
2
 
3
- Copyright (c) 2018-2024 Yegor Bugayenko
3
+ Copyright (c) 2018-2025 Yegor Bugayenko
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the 'Software'), to deal
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2024 Yegor Bugayenko
3
+ # Copyright (c) 2018-2025 Yegor Bugayenko
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the 'Software'), to deal
@@ -2,7 +2,7 @@
2
2
 
3
3
  # (The MIT License)
4
4
  #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
5
+ # Copyright (c) 2018-2025 Yegor Bugayenko
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the 'Software'), to deal
@@ -25,7 +25,7 @@
25
25
  # The module for all classes.
26
26
  #
27
27
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
- # Copyright:: Copyright (c) 2018-2024 Yegor Bugayenko
28
+ # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
29
29
  # License:: MIT
30
30
  module RandomPort
31
31
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # (The MIT License)
4
4
  #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
5
+ # Copyright (c) 2018-2025 Yegor Bugayenko
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the 'Software'), to deal
@@ -44,7 +44,7 @@ require_relative 'module'
44
44
  # passing <tt>FALSE</tt>.
45
45
  #
46
46
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
47
- # Copyright:: Copyright (c) 2018-2024 Yegor Bugayenko
47
+ # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
48
48
  # License:: MIT
49
49
  class RandomPort::Pool
50
50
  # If can't acquire by time out.
@@ -55,12 +55,13 @@ class RandomPort::Pool
55
55
  # Ctor.
56
56
  # @param [Boolean] sync Set it to FALSE if you want this pool to be NOT thread-safe
57
57
  # @param [Integer] limit Set the maximum number of ports in the pool
58
- def initialize(sync: true, limit: 65_536)
58
+ # @param [Integer] start The next port to try
59
+ def initialize(sync: true, limit: 65_536, start: 1025)
59
60
  @ports = []
60
61
  @sync = sync
61
62
  @monitor = Monitor.new
62
63
  @limit = limit
63
- @next = 1024
64
+ @next = start
64
65
  end
65
66
 
66
67
  # Application wide pool of ports
@@ -96,25 +97,14 @@ class RandomPort::Pool
96
97
  "for #{total} port(s), after #{attempt} attempts in #{start.ago}"
97
98
  end
98
99
  attempt += 1
99
- opts = safe do
100
- next if @ports.count + total > @limit
101
- opts = Array.new(0, total)
102
- begin
103
- (0..(total - 1)).each do |i|
104
- opts[i] = take(i.zero? ? @next : opts[i - 1] + 1)
105
- end
106
- rescue Errno::EADDRINUSE, SocketError
107
- next
108
- end
109
- next if opts.any? { |p| @ports.include?(p) }
110
- d = total * (total - 1) / 2
111
- next unless opts.inject(&:+) - (total * opts.min) == d
112
- @ports += opts
113
- opts
100
+ opts = safe { group(total) }
101
+ if opts.nil?
102
+ @next += 1
103
+ else
104
+ @next = opts.max + 1
114
105
  end
115
- next if opts.nil?
116
- @next = opts.max + 1
117
106
  @next = 0 if @next > 65_535
107
+ next if opts.nil?
118
108
  opts = opts[0] if total == 1
119
109
  return opts unless block_given?
120
110
  begin
@@ -126,6 +116,8 @@ class RandomPort::Pool
126
116
  end
127
117
 
128
118
  # Return it/them back to the pool.
119
+ # @param [Integer] port TCP port number to release
120
+ # @return nil
129
121
  def release(port)
130
122
  safe do
131
123
  if port.is_a?(Array)
@@ -138,14 +130,38 @@ class RandomPort::Pool
138
130
 
139
131
  private
140
132
 
141
- # Find one possible TCP port.
142
- # @param [Integer] opt Suggested port number
143
- # @return [Integer] Port found
144
- def take(opt = 0)
145
- server = TCPServer.new('127.0.0.1', opt)
146
- p = server.addr[1]
147
- server.close
148
- p
133
+ # Take a group of ports, if possible.
134
+ # @param [Integer] total How many ports to take
135
+ # @return [Array<Integer>|nil] Ports found or NIL if impossible now
136
+ def group(total)
137
+ return nil if @ports.count + total > @limit
138
+ opts = Array.new(0, total)
139
+ begin
140
+ (0..(total - 1)).each do |i|
141
+ port = i.zero? ? @next : opts[i - 1] + 1
142
+ opts[i] = take(port)
143
+ end
144
+ rescue Errno::EADDRINUSE, SocketError
145
+ return nil
146
+ end
147
+ return nil if opts.any? { |p| @ports.include?(p) }
148
+ d = total * (total - 1) / 2
149
+ return nil unless opts.inject(&:+) - (total * opts.min) == d
150
+ @ports += opts
151
+ opts
152
+ end
153
+
154
+ # Find one possible TCP port or raise exception if this port can't be used.
155
+ #
156
+ # If port is occupied, this method raises an error (+Errno::EADDRINUSE+).
157
+ #
158
+ # @param [Integer] port Suggested port number
159
+ # @return [Integer] The same port number
160
+ def take(port)
161
+ ['127.0.0.1', '::1', '0.0.0.0', 'localhost'].each do |host|
162
+ TCPServer.new(host, port).close
163
+ end
164
+ port
149
165
  end
150
166
 
151
167
  def safe(&block)
data/lib/random-port.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # (The MIT License)
4
4
  #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
5
+ # Copyright (c) 2018-2025 Yegor Bugayenko
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the 'Software'), to deal
data/random-port.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # (The MIT License)
4
4
  #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
5
+ # Copyright (c) 2018-2025 Yegor Bugayenko
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the 'Software'), to deal
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
27
27
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
28
28
  s.required_ruby_version = '>=2.3'
29
29
  s.name = 'random-port'
30
- s.version = '0.7.3'
30
+ s.version = '0.7.5'
31
31
  s.license = 'MIT'
32
32
  s.summary = 'Random TCP port'
33
33
  s.description = 'Reserves a random TCP port'
data/test/test__helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2024 Yegor Bugayenko
3
+ # Copyright (c) 2018-2025 Yegor Bugayenko
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the 'Software'), to deal
@@ -24,4 +24,5 @@ $stdout.sync = true
24
24
 
25
25
  require 'simplecov'
26
26
  SimpleCov.start
27
+
27
28
  require 'minitest/autorun'
data/test/test_pool.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # (The MIT License)
4
4
  #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
5
+ # Copyright (c) 2018-2025 Yegor Bugayenko
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the 'Software'), to deal
@@ -23,13 +23,15 @@
23
23
  # SOFTWARE.
24
24
 
25
25
  require 'minitest/autorun'
26
- require 'threads'
26
+ require 'qbash'
27
+ require 'shellwords'
27
28
  require 'socket'
29
+ require 'threads'
28
30
  require_relative '../lib/random-port/pool'
29
31
 
30
32
  # Pool test.
31
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
- # Copyright:: Copyright (c) 2018-2024 Yegor Bugayenko
34
+ # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
33
35
  # License:: MIT
34
36
  class RandomPort::TestPool < Minitest::Test
35
37
  def test_acquires_and_releases
@@ -37,8 +39,8 @@ class RandomPort::TestPool < Minitest::Test
37
39
  port = pool.acquire
38
40
  server = TCPServer.new('localhost', port)
39
41
  server.close
40
- assert(!port.nil?)
41
- assert(port.positive?)
42
+ refute_nil(port)
43
+ assert_predicate(port, :positive?)
42
44
  pool.release(port)
43
45
  end
44
46
 
@@ -56,11 +58,56 @@ class RandomPort::TestPool < Minitest::Test
56
58
  assert_equal(0, pool.size)
57
59
  end
58
60
 
61
+ def test_skips_truly_busy_port
62
+ port = RandomPort::Pool.new.acquire
63
+ server = TCPServer.new('127.0.0.1', port)
64
+ other = RandomPort::Pool.new(start: port).acquire
65
+ refute_equal(other, port)
66
+ server.close
67
+ end
68
+
69
+ def test_skips_externally_busy_port
70
+ skip 'Not supported on Windows' if Gem.win_platform?
71
+ ['127.0.0.1', 'localhost', '::1', '0.0.0.0'].each do |host|
72
+ Dir.mktmpdir do |home|
73
+ port = RandomPort::Pool.new.acquire
74
+ started = File.join(home, 'started.txt')
75
+ enough = File.join(home, 'enough.txt')
76
+ t =
77
+ Thread.new do
78
+ qbash(
79
+ [
80
+ 'ruby', '-e',
81
+ Shellwords.escape(
82
+ "
83
+ require 'socket'
84
+ require 'fileutils'
85
+ TCPServer.new('#{host}', #{port})
86
+ FileUtils.touch('#{started}')
87
+ loop do
88
+ break if File.exist?('#{enough}')
89
+ end
90
+ "
91
+ )
92
+ ]
93
+ )
94
+ end
95
+ loop do
96
+ break if File.exist?(started)
97
+ end
98
+ other = RandomPort::Pool.new(start: port).acquire
99
+ FileUtils.touch(enough)
100
+ t.join
101
+ refute_equal(other, port)
102
+ end
103
+ end
104
+ end
105
+
59
106
  def test_acquires_and_releases_three_ports_in_block
60
107
  pool = RandomPort::Pool.new(limit: 3)
61
108
  assert_equal(0, pool.size)
62
109
  pool.acquire(3, timeout: 16) do |ports|
63
- assert(ports.is_a?(Array))
110
+ assert_kind_of(Array, ports)
64
111
  assert_equal(3, ports.count)
65
112
  assert_equal(3, pool.size)
66
113
  ports.each do |p|
@@ -73,8 +120,8 @@ class RandomPort::TestPool < Minitest::Test
73
120
 
74
121
  def test_acquires_and_releases_in_block
75
122
  result = RandomPort::Pool.new.acquire do |port|
76
- assert(!port.nil?)
77
- assert(port.positive?)
123
+ refute_nil(port)
124
+ assert_predicate(port, :positive?)
78
125
  123
79
126
  end
80
127
  assert_equal(123, result)
@@ -94,18 +141,18 @@ class RandomPort::TestPool < Minitest::Test
94
141
 
95
142
  def test_acquires_and_releases_safely
96
143
  pool = RandomPort::Pool.new
97
- assert_raises do
144
+ assert_raises(StandardError) do
98
145
  pool.acquire do
99
146
  raise 'Itended'
100
147
  end
101
148
  end
102
- assert(pool.count.zero?)
149
+ assert_predicate(pool.count, :zero?)
103
150
  end
104
151
 
105
152
  def test_acquires_and_releases_from_singleton
106
153
  RandomPort::Pool::SINGLETON.acquire do |port|
107
- assert(!port.nil?)
108
- assert(port.positive?)
154
+ refute_nil(port)
155
+ assert_predicate(port, :positive?)
109
156
  end
110
157
  end
111
158
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: random-port
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-29 00:00:00.000000000 Z
11
+ date: 2025-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tago