net-ping 2.0.8 → 2.1.0
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 +5 -5
- data/CHANGES +28 -1
- data/CLAUDE.md +89 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +34 -16
- data/README.md +5 -3
- data/Rakefile +93 -8
- data/docs/superpowers/plans/2026-07-25-platform-specific-gems.md +317 -0
- data/docs/superpowers/specs/2026-07-25-platform-specific-gems-design.md +71 -0
- data/lib/net/ping/external.rb +5 -3
- data/lib/net/ping/ping.rb +2 -3
- data/lib/net/ping/version.rb +6 -0
- data/net-ping-universal-linux.gemspec +4 -0
- data/net-ping-universal-mingw-ucrt.gemspec +7 -0
- data/net-ping.gemspec +4 -16
- data/test/test_net_ping.rb +2 -4
- data/test/test_net_ping_external.rb +89 -0
- data/test/test_net_ping_gem_packaging.rb +333 -0
- data/test/test_net_ping_http.rb +27 -31
- data/test/test_net_ping_icmp.rb +6 -5
- data/test/test_net_ping_tcp.rb +2 -1
- metadata +14 -8
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Platform-specific gem dependencies
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Resolve issue #31 by publishing platform-specific `net-ping` gems whose
|
|
6
|
+
runtime dependencies accurately describe the requirements for ICMP support.
|
|
7
|
+
The solution also corrects the equivalent Linux `cap2` metadata omission.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
The release produces three gems with the same name and version:
|
|
12
|
+
|
|
13
|
+
| Gem platform | Additional runtime dependency |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `ruby` | None |
|
|
16
|
+
| `universal-linux` | `cap2 (>= 0.2.2)` |
|
|
17
|
+
| `universal-mingw-ucrt` | `win32-security (>= 0.2.0)` |
|
|
18
|
+
|
|
19
|
+
Only the current RubyInstaller `mingw-ucrt` platform is supported for Windows.
|
|
20
|
+
Legacy `mingw32` is out of scope.
|
|
21
|
+
|
|
22
|
+
The CI workflow builds and validates these gems, but does not publish them.
|
|
23
|
+
The existing release process remains responsible for pushing all three files
|
|
24
|
+
to RubyGems.
|
|
25
|
+
|
|
26
|
+
## Design
|
|
27
|
+
|
|
28
|
+
`net-ping.gemspec` becomes the OS-independent Ruby gemspec. It must not
|
|
29
|
+
inspect the host OS or add OS-specific runtime dependencies.
|
|
30
|
+
|
|
31
|
+
`net-ping-universal-linux.gemspec` loads the base specification, changes its
|
|
32
|
+
platform to `universal-linux`, and adds `cap2`. The platform is intentionally
|
|
33
|
+
CPU-independent so RubyGems can select it for supported Linux CPU variants.
|
|
34
|
+
|
|
35
|
+
`net-ping-universal-mingw-ucrt.gemspec` loads the same base specification,
|
|
36
|
+
changes its platform to `universal-mingw-ucrt`, and adds `win32-security`.
|
|
37
|
+
|
|
38
|
+
The Rake gem namespace explicitly enumerates the three gemspecs. `gem:create`
|
|
39
|
+
cleans old gem artifacts and builds all three. `gem:install` selects the
|
|
40
|
+
specific generated specification compatible with the local platform, falling
|
|
41
|
+
back to the Ruby specification when no platform-specific specification
|
|
42
|
+
matches, then installs its artifact.
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
Add `gem:check`, which reads each generated gem through RubyGems and fails
|
|
47
|
+
when its platform or runtime dependencies differ from this specification:
|
|
48
|
+
|
|
49
|
+
- Ruby gem: platform `ruby`; neither `cap2` nor `win32-security`.
|
|
50
|
+
- Linux gem: platform `universal-linux`; includes `cap2`.
|
|
51
|
+
- Windows gem: platform `universal-mingw-ucrt`; includes `win32-security`.
|
|
52
|
+
|
|
53
|
+
The existing GitHub Actions matrix continues to run the test suite. It also
|
|
54
|
+
runs `gem:create` and `gem:check` on both Ubuntu and Windows. A missing,
|
|
55
|
+
malformed, or incorrectly attributed dependency causes the job to fail.
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
Update the prerequisite/install documentation to state that RubyGems chooses
|
|
60
|
+
the platform-specific package and installs the Linux or Windows ICMP
|
|
61
|
+
dependency automatically.
|
|
62
|
+
|
|
63
|
+
Add a `CHANGES` entry describing the corrected platform-specific dependency
|
|
64
|
+
metadata.
|
|
65
|
+
|
|
66
|
+
## Non-goals
|
|
67
|
+
|
|
68
|
+
- Publishing gems from CI.
|
|
69
|
+
- Supporting legacy `mingw32`.
|
|
70
|
+
- Changing ICMP runtime behavior.
|
|
71
|
+
- Altering non-ICMP protocol implementations or tests.
|
data/lib/net/ping/external.rb
CHANGED
|
@@ -66,7 +66,7 @@ module Net
|
|
|
66
66
|
bool = true # Success, at least one response.
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
if err
|
|
69
|
+
if err && (err =~ /warning/i)
|
|
70
70
|
@warning = err.chomp
|
|
71
71
|
end
|
|
72
72
|
when 2
|
|
@@ -116,8 +116,10 @@ module Net
|
|
|
116
116
|
pcmd += ['-i', interval.to_s] unless RbConfig::CONFIG['busybox']
|
|
117
117
|
when /aix/i
|
|
118
118
|
pcmd += ['-c', count.to_s, '-w', timeout.to_s, host]
|
|
119
|
+
when /freebsd/i
|
|
120
|
+
pcmd += ['-c', count.to_s, '-x', timeout.to_s, host]
|
|
119
121
|
when /bsd|osx|mach|darwin/i
|
|
120
|
-
pcmd += ['-c', count.to_s, '-
|
|
122
|
+
pcmd += ['-c', count.to_s, '-i', interval.to_s, host]
|
|
121
123
|
when /solaris|sunos/i
|
|
122
124
|
pcmd += [host, timeout.to_s]
|
|
123
125
|
when /hpux/i
|
|
@@ -147,7 +149,7 @@ module Net
|
|
|
147
149
|
bool = true # Success, at least one response.
|
|
148
150
|
end
|
|
149
151
|
|
|
150
|
-
if err
|
|
152
|
+
if err && (err =~ /warning/i)
|
|
151
153
|
@warning = err.chomp
|
|
152
154
|
end
|
|
153
155
|
when 2
|
data/lib/net/ping/ping.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require 'socket'
|
|
2
2
|
require 'timeout'
|
|
3
3
|
|
|
4
|
+
require_relative 'version'
|
|
5
|
+
|
|
4
6
|
# The Net module serves as a namespace only.
|
|
5
7
|
#
|
|
6
8
|
module Net
|
|
@@ -9,9 +11,6 @@ module Net
|
|
|
9
11
|
# types. You should not instantiate this class directly.
|
|
10
12
|
#
|
|
11
13
|
class Ping
|
|
12
|
-
# The version of the net-ping library.
|
|
13
|
-
VERSION = '1.7.6'
|
|
14
|
-
|
|
15
14
|
# The host to ping. In the case of Ping::HTTP, this is the URI.
|
|
16
15
|
attr_accessor :host
|
|
17
16
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
spec = Gem::Specification.load('net-ping.gemspec').dup
|
|
2
|
+
spec.platform = Gem::Platform.new(['universal', 'mingw-ucrt'])
|
|
3
|
+
spec.add_dependency('win32-security', '>= 0.2.0')
|
|
4
|
+
|
|
5
|
+
# No longer a default gem as of Ruby 4.0. Used by the WMI ping class.
|
|
6
|
+
spec.add_dependency('win32ole', '>= 1.8.8')
|
|
7
|
+
spec
|
data/net-ping.gemspec
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
require 'rbconfig'
|
|
1
|
+
require_relative "lib/net/ping/version"
|
|
3
2
|
|
|
4
3
|
Gem::Specification.new do |spec|
|
|
5
4
|
spec.name = 'net-ping'
|
|
6
|
-
spec.version =
|
|
5
|
+
spec.version = Net::Ping::VERSION
|
|
7
6
|
spec.license = 'Artistic 2.0'
|
|
8
7
|
spec.author = 'Chris Chernesky'
|
|
9
8
|
spec.email = 'chris.netping@tinderglow.com'
|
|
10
9
|
spec.homepage = 'https://github.com/chernesk/net-ping'
|
|
11
10
|
spec.summary = 'A ping interface for Ruby.'
|
|
12
11
|
spec.test_file = 'test/test_net_ping.rb'
|
|
13
|
-
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
|
12
|
+
spec.files = Dir['**/*'].reject { |f| f.include?('git') || File.extname(f) == '.gem' }
|
|
14
13
|
spec.metadata = {
|
|
15
14
|
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
|
16
15
|
'changelog_uri' => "#{spec.homepage}/blob/master/CHANGES",
|
|
@@ -25,21 +24,10 @@ Gem::Specification.new do |spec|
|
|
|
25
24
|
spec.required_ruby_version = ">= 1.9.3"
|
|
26
25
|
|
|
27
26
|
spec.add_development_dependency('test-unit', '>= 0')
|
|
28
|
-
spec.add_development_dependency('
|
|
27
|
+
spec.add_development_dependency('webmock', '>= 0')
|
|
29
28
|
spec.add_development_dependency('rake', '>= 0')
|
|
30
29
|
spec.add_development_dependency('pry-byebug', '>= 0')
|
|
31
30
|
|
|
32
|
-
if File::ALT_SEPARATOR
|
|
33
|
-
require 'rbconfig'
|
|
34
|
-
arch = RbConfig::CONFIG['build_os'] || 'mingw32' # JRuby
|
|
35
|
-
spec.platform = Gem::Platform.new(['universal', arch])
|
|
36
|
-
spec.platform.version = nil
|
|
37
|
-
|
|
38
|
-
# Used for icmp pings.
|
|
39
|
-
spec.add_dependency('win32-security', '>= 0.2.0')
|
|
40
|
-
spec.add_dependency('cap2', '>= 0.2.2')
|
|
41
|
-
end
|
|
42
|
-
|
|
43
31
|
spec.description = <<-EOF
|
|
44
32
|
The net-ping library provides a ping interface for Ruby. It includes
|
|
45
33
|
separate TCP, HTTP, LDAP, ICMP, UDP, WMI (for Windows) and external ping
|
data/test/test_net_ping.rb
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
# class test won't be run unless this is run as a privileged process.
|
|
6
6
|
######################################################################
|
|
7
7
|
require 'test_net_ping_external'
|
|
8
|
+
require 'test_net_ping_gem_packaging'
|
|
8
9
|
require 'test_net_ping_http'
|
|
9
10
|
require 'test_net_ping_tcp'
|
|
10
11
|
require 'test_net_ping_udp'
|
|
11
|
-
require 'fakeweb'
|
|
12
12
|
|
|
13
13
|
if File::ALT_SEPARATOR
|
|
14
14
|
require 'win32/security'
|
|
@@ -39,8 +39,6 @@ end
|
|
|
39
39
|
|
|
40
40
|
class TC_Net_Ping < Test::Unit::TestCase
|
|
41
41
|
def test_net_ping_version
|
|
42
|
-
assert_equal('
|
|
42
|
+
assert_equal(Gem::Specification.load('net-ping.gemspec').version.to_s, Net::Ping::VERSION)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
|
-
|
|
46
|
-
FakeWeb.allow_net_connect = false
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
#########################################################################
|
|
11
11
|
require 'test-unit'
|
|
12
12
|
require 'net/ping/external'
|
|
13
|
+
require 'mkmf'
|
|
14
|
+
require 'stringio'
|
|
13
15
|
|
|
14
16
|
class TC_Net_Ping_External < Test::Unit::TestCase
|
|
15
17
|
def setup
|
|
@@ -47,6 +49,7 @@ class TC_Net_Ping_External < Test::Unit::TestCase
|
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
test "pinging a good host returns true" do
|
|
52
|
+
omit_if(find_executable0('ping').nil?)
|
|
50
53
|
assert_true(@pe.ping?)
|
|
51
54
|
end
|
|
52
55
|
|
|
@@ -55,12 +58,14 @@ class TC_Net_Ping_External < Test::Unit::TestCase
|
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
test "duration basic functionality" do
|
|
61
|
+
omit_if(find_executable0('ping').nil?)
|
|
58
62
|
assert_nothing_raised{ @pe.ping }
|
|
59
63
|
assert_respond_to(@pe, :duration)
|
|
60
64
|
assert_kind_of(Float, @pe.duration)
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
test "duration is unset if a bad ping follows a good ping" do
|
|
68
|
+
omit_if(find_executable0('ping').nil?)
|
|
64
69
|
assert_nothing_raised{ @pe.ping }
|
|
65
70
|
assert_not_nil(@pe.duration)
|
|
66
71
|
assert_false(@pe.ping?(@bogus))
|
|
@@ -111,6 +116,7 @@ class TC_Net_Ping_External < Test::Unit::TestCase
|
|
|
111
116
|
end
|
|
112
117
|
|
|
113
118
|
test "pinging a good host results in no exception data" do
|
|
119
|
+
omit_if(find_executable0('ping').nil?)
|
|
114
120
|
assert_nothing_raised{ @pe.ping }
|
|
115
121
|
assert_nil(@pe.exception)
|
|
116
122
|
end
|
|
@@ -134,6 +140,89 @@ class TC_Net_Ping_External < Test::Unit::TestCase
|
|
|
134
140
|
assert_false(@bad.ping?)
|
|
135
141
|
end
|
|
136
142
|
|
|
143
|
+
test "ping6 uses timeout for FreeBSD" do
|
|
144
|
+
command = nil
|
|
145
|
+
original_os = RbConfig::CONFIG['host_os']
|
|
146
|
+
original_popen3 = Open3.method(:popen3)
|
|
147
|
+
|
|
148
|
+
RbConfig::CONFIG['host_os'] = 'freebsd'
|
|
149
|
+
Open3.define_singleton_method(:popen3) do |*args, &block|
|
|
150
|
+
command = args
|
|
151
|
+
stdin = StringIO.new
|
|
152
|
+
stdout = StringIO.new
|
|
153
|
+
stderr = StringIO.new
|
|
154
|
+
status = Struct.new(:exitstatus).new(0)
|
|
155
|
+
thread = Struct.new(:value).new(status)
|
|
156
|
+
block.call(stdin, stdout, stderr, thread)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@pe.ping6('example.test', 2, 3, 4)
|
|
160
|
+
|
|
161
|
+
assert_equal(['ping6', '-c', '2', '-x', '4', 'example.test'], command)
|
|
162
|
+
ensure
|
|
163
|
+
RbConfig::CONFIG['host_os'] = original_os
|
|
164
|
+
Open3.define_singleton_method(:popen3, original_popen3)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
test "ping6 uses interval for macOS" do
|
|
168
|
+
command = nil
|
|
169
|
+
original_os = RbConfig::CONFIG['host_os']
|
|
170
|
+
original_popen3 = Open3.method(:popen3)
|
|
171
|
+
|
|
172
|
+
RbConfig::CONFIG['host_os'] = 'darwin'
|
|
173
|
+
Open3.define_singleton_method(:popen3) do |*args, &block|
|
|
174
|
+
command = args
|
|
175
|
+
stdin = StringIO.new
|
|
176
|
+
stdout = StringIO.new
|
|
177
|
+
stderr = StringIO.new
|
|
178
|
+
status = Struct.new(:exitstatus).new(0)
|
|
179
|
+
thread = Struct.new(:value).new(status)
|
|
180
|
+
block.call(stdin, stdout, stderr, thread)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
@pe.ping6('example.test', 2, 3, 4)
|
|
184
|
+
|
|
185
|
+
assert_equal(['ping6', '-c', '2', '-i', '3', 'example.test'], command)
|
|
186
|
+
ensure
|
|
187
|
+
RbConfig::CONFIG['host_os'] = original_os
|
|
188
|
+
Open3.define_singleton_method(:popen3, original_popen3)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
test "ping6 succeeds without an exception when stderr is empty" do
|
|
192
|
+
stdin = StringIO.new
|
|
193
|
+
stdout = StringIO.new('64 bytes from ::1')
|
|
194
|
+
stderr = StringIO.new
|
|
195
|
+
status = Struct.new(:exitstatus).new(0)
|
|
196
|
+
thread = Struct.new(:value).new(status)
|
|
197
|
+
popen3 = Open3.method(:popen3)
|
|
198
|
+
nil_match = NilClass.instance_method(:=~) if NilClass.instance_methods(false).include?(:=~)
|
|
199
|
+
false_match = FalseClass.instance_method(:=~) if FalseClass.instance_methods(false).include?(:=~)
|
|
200
|
+
|
|
201
|
+
Open3.singleton_class.send(:define_method, :popen3) do |*args, &block|
|
|
202
|
+
block.call(stdin, stdout, stderr, thread)
|
|
203
|
+
end
|
|
204
|
+
[NilClass, FalseClass].each do |klass|
|
|
205
|
+
klass.send(:define_method, :=~) do |*args|
|
|
206
|
+
raise NoMethodError, "undefined method '=~' for #{self.inspect}"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
assert_true(@pe.ping6('::1'))
|
|
211
|
+
assert_nil(@pe.exception)
|
|
212
|
+
ensure
|
|
213
|
+
Open3.singleton_class.send(:define_method, :popen3, popen3) if popen3
|
|
214
|
+
if nil_match
|
|
215
|
+
NilClass.send(:define_method, :=~, nil_match)
|
|
216
|
+
else
|
|
217
|
+
NilClass.send(:remove_method, :=~)
|
|
218
|
+
end
|
|
219
|
+
if false_match
|
|
220
|
+
FalseClass.send(:define_method, :=~, false_match)
|
|
221
|
+
else
|
|
222
|
+
FalseClass.send(:remove_method, :=~)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
137
226
|
def teardown
|
|
138
227
|
@host = nil
|
|
139
228
|
@bogus = nil
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'rubygems/package'
|
|
4
|
+
require 'test/unit'
|
|
5
|
+
|
|
6
|
+
class TestNetPingGemPackaging < Test::Unit::TestCase
|
|
7
|
+
GemfileRecorder = Struct.new(:dependencies) do
|
|
8
|
+
def initialize
|
|
9
|
+
super({})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def source(*); end
|
|
13
|
+
def gemspec(*); end
|
|
14
|
+
|
|
15
|
+
def gem(name, *requirements)
|
|
16
|
+
dependencies[name] = requirements
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
EXPECTATIONS = {
|
|
21
|
+
'net-ping.gemspec' => ['ruby', {}],
|
|
22
|
+
'net-ping-universal-linux.gemspec' => ['universal-linux', {'cap2' => '>= 0.2.2'}],
|
|
23
|
+
'net-ping-universal-mingw-ucrt.gemspec' => [
|
|
24
|
+
'universal-mingw-ucrt',
|
|
25
|
+
{'win32-security' => '>= 0.2.0', 'win32ole' => '>= 1.8.8'}
|
|
26
|
+
]
|
|
27
|
+
}.freeze
|
|
28
|
+
ROOT = File.expand_path('..', __dir__)
|
|
29
|
+
|
|
30
|
+
def test_platforms_and_runtime_dependencies
|
|
31
|
+
gem_files = nil
|
|
32
|
+
specifications = load_specifications
|
|
33
|
+
|
|
34
|
+
Dir.chdir(ROOT) do
|
|
35
|
+
gem_files = specifications.map { |_, spec| Gem::Package.build(spec) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
specifications.each do |path, spec|
|
|
39
|
+
expected_platform, expected_dependencies = EXPECTATIONS.fetch(path)
|
|
40
|
+
packaged_spec = Gem::Package.new(File.join(ROOT, spec.file_name)).spec
|
|
41
|
+
dependencies = packaged_spec.runtime_dependencies.each_with_object({}) do |dependency, memo|
|
|
42
|
+
memo[dependency.name] = dependency.requirement.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
assert_equal(expected_dependencies, dependencies, path)
|
|
46
|
+
|
|
47
|
+
if platform_round_trips_for_test?(expected_platform)
|
|
48
|
+
assert_equal(expected_platform, packaged_spec.platform.to_s, path)
|
|
49
|
+
else
|
|
50
|
+
omit("this RubyGems (#{Gem::VERSION}) cannot round-trip the #{expected_platform.inspect} platform string")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
ensure
|
|
54
|
+
delete_files(gem_files)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_rake_create_builds_all_expected_artifacts_and_validates_them
|
|
58
|
+
stdout, stderr, status = run_rake('clean', 'gem:create', 'gem:check')
|
|
59
|
+
|
|
60
|
+
assert(status.success?, [stdout, stderr].join)
|
|
61
|
+
assert_equal(expected_gem_files, built_gem_files)
|
|
62
|
+
ensure
|
|
63
|
+
delete_files(built_gem_files)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_gem_create_excludes_generated_artifacts_from_spec_files_and_keeps_bundle_exec_usable
|
|
67
|
+
stdout, stderr, status = run_rake('clean', 'gem:create')
|
|
68
|
+
|
|
69
|
+
assert(status.success?, [stdout, stderr].join)
|
|
70
|
+
load_specifications.each do |path, spec|
|
|
71
|
+
assert_equal([], spec.files.grep(/\.gem$/).sort, path)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
stdout, stderr, status = run_bundle_exec('ruby', '-e', 'puts :bundle_exec_after_gem_create')
|
|
75
|
+
assert(status.success?, [stdout, stderr].join)
|
|
76
|
+
assert_equal("bundle_exec_after_gem_create\n", stdout)
|
|
77
|
+
ensure
|
|
78
|
+
delete_files(built_gem_files)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_gem_check_uses_fixed_expectations_instead_of_loaded_specifications
|
|
82
|
+
stdout, stderr, status = run_rake('clean', 'gem:create')
|
|
83
|
+
|
|
84
|
+
assert(status.success?, [stdout, stderr].join)
|
|
85
|
+
|
|
86
|
+
specifications = load_specifications.map do |path, spec|
|
|
87
|
+
if path == 'net-ping-universal-linux.gemspec'
|
|
88
|
+
[path, specification_with_reported_platform(spec, Gem::Platform::RUBY)]
|
|
89
|
+
else
|
|
90
|
+
[path, spec]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
with_rakefile do |application|
|
|
95
|
+
with_stubbed_load_gem_specifications(specifications.map(&:last)) do
|
|
96
|
+
assert_nothing_raised { application['gem:check'].invoke }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
ensure
|
|
100
|
+
delete_files(built_gem_files)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_gem_install_selects_local_compatible_specification
|
|
104
|
+
with_rakefile do
|
|
105
|
+
assert_equal(expected_install_platform, select_install_specification.platform.to_s)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_gemfile_adds_win32_security_only_for_windows_development
|
|
110
|
+
assert_nil(gemfile_dependencies_for(win_platform: false)['win32-security'])
|
|
111
|
+
assert_equal(
|
|
112
|
+
['>= 0.2.0'],
|
|
113
|
+
gemfile_dependencies_for(win_platform: true)['win32-security']
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_with_unbundled_env_prefers_with_unbundled_env_when_available
|
|
118
|
+
calls = []
|
|
119
|
+
|
|
120
|
+
with_rakefile do
|
|
121
|
+
with_stubbed_bundler_methods(
|
|
122
|
+
:with_unbundled_env => proc do |&block|
|
|
123
|
+
calls << :with_unbundled_env
|
|
124
|
+
block.call
|
|
125
|
+
end,
|
|
126
|
+
:with_clean_env => proc do |&block|
|
|
127
|
+
calls << :with_clean_env
|
|
128
|
+
block.call
|
|
129
|
+
end
|
|
130
|
+
) do
|
|
131
|
+
assert_equal(:ran, with_unbundled_env { calls << :block; :ran })
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
assert_equal([:with_unbundled_env, :block], calls)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_with_unbundled_env_falls_back_to_with_clean_env
|
|
139
|
+
calls = []
|
|
140
|
+
|
|
141
|
+
with_rakefile do
|
|
142
|
+
with_stubbed_bundler_methods(
|
|
143
|
+
:with_clean_env => proc do |&block|
|
|
144
|
+
calls << :with_clean_env
|
|
145
|
+
block.call
|
|
146
|
+
end
|
|
147
|
+
) do
|
|
148
|
+
assert_equal(:ran, with_unbundled_env { calls << :block; :ran })
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
assert_equal([:with_clean_env, :block], calls)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_platform_installable_prefers_installable_when_available
|
|
156
|
+
spec = Struct.new(:platform).new(:preferred_platform)
|
|
157
|
+
calls = []
|
|
158
|
+
|
|
159
|
+
with_rakefile do
|
|
160
|
+
with_stubbed_platform_methods(
|
|
161
|
+
:installable? => proc do |candidate|
|
|
162
|
+
calls << [:installable?, candidate.platform]
|
|
163
|
+
true
|
|
164
|
+
end,
|
|
165
|
+
:match => proc do |platform|
|
|
166
|
+
calls << [:match, platform]
|
|
167
|
+
false
|
|
168
|
+
end
|
|
169
|
+
) do
|
|
170
|
+
assert_equal(true, platform_installable?(spec))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
assert_equal([[:installable?, :preferred_platform]], calls)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_platform_installable_falls_back_to_match
|
|
178
|
+
spec = Struct.new(:platform).new(:fallback_platform)
|
|
179
|
+
calls = []
|
|
180
|
+
|
|
181
|
+
with_rakefile do
|
|
182
|
+
with_stubbed_platform_methods(
|
|
183
|
+
:match => proc do |platform|
|
|
184
|
+
calls << [:match, platform]
|
|
185
|
+
true
|
|
186
|
+
end
|
|
187
|
+
) do
|
|
188
|
+
assert_equal(true, platform_installable?(spec))
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
assert_equal([[:match, :fallback_platform]], calls)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def load_specifications
|
|
198
|
+
Dir.chdir(ROOT) do
|
|
199
|
+
EXPECTATIONS.keys.map do |path|
|
|
200
|
+
assert(File.exist?(path), "#{path} does not exist")
|
|
201
|
+
|
|
202
|
+
loaded_spec = Gem::Specification.load(path)
|
|
203
|
+
spec = loaded_spec ? loaded_spec.dup : nil
|
|
204
|
+
assert_not_nil(spec, "expected #{path} to load")
|
|
205
|
+
|
|
206
|
+
[path, spec]
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def expected_gem_files
|
|
212
|
+
load_specifications.map { |_, spec| spec.file_name }.sort
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def built_gem_files
|
|
216
|
+
Dir.chdir(ROOT) { Dir['net-ping-*.gem'].sort }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def expected_install_platform
|
|
220
|
+
specifications = load_specifications.map(&:last)
|
|
221
|
+
specific = specifications.find do |spec|
|
|
222
|
+
spec.platform != Gem::Platform::RUBY && platform_installable_for_test?(spec)
|
|
223
|
+
end
|
|
224
|
+
(specific || specifications.find { |spec| spec.platform == Gem::Platform::RUBY }).platform.to_s
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def run_rake(*tasks, env: {})
|
|
228
|
+
Open3.capture3(env, Gem.ruby, '-S', 'bundle', 'exec', 'rake', *tasks, chdir: ROOT)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def run_bundle_exec(*command, env: {})
|
|
232
|
+
Open3.capture3(env, Gem.ruby, '-S', 'bundle', 'exec', *command, chdir: ROOT)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def with_rakefile
|
|
236
|
+
original = Rake.application
|
|
237
|
+
application = Rake::Application.new
|
|
238
|
+
Rake.application = application
|
|
239
|
+
|
|
240
|
+
Dir.chdir(ROOT) do
|
|
241
|
+
load File.join(ROOT, 'Rakefile')
|
|
242
|
+
yield(application)
|
|
243
|
+
end
|
|
244
|
+
ensure
|
|
245
|
+
Rake.application = original
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def gemfile_dependencies_for(win_platform:)
|
|
249
|
+
recorder = GemfileRecorder.new
|
|
250
|
+
singleton_class = class << Gem; self; end
|
|
251
|
+
original = singleton_class.instance_method(:win_platform?)
|
|
252
|
+
singleton_class.send(:define_method, :win_platform?) { win_platform }
|
|
253
|
+
recorder.instance_eval(File.read(File.join(ROOT, 'Gemfile')), File.join(ROOT, 'Gemfile'))
|
|
254
|
+
|
|
255
|
+
recorder.dependencies
|
|
256
|
+
ensure
|
|
257
|
+
singleton_class.send(:define_method, :win_platform?, original) if singleton_class && original
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def delete_files(paths)
|
|
261
|
+
Array(paths).each do |path|
|
|
262
|
+
file = File.join(ROOT, path)
|
|
263
|
+
File.delete(file) if File.exist?(file)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def specification_with_reported_platform(spec, platform)
|
|
268
|
+
file_name = spec.file_name
|
|
269
|
+
mutated = spec.dup
|
|
270
|
+
mutated.platform = platform
|
|
271
|
+
mutated.define_singleton_method(:file_name) { file_name }
|
|
272
|
+
mutated
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def with_stubbed_load_gem_specifications(specifications)
|
|
276
|
+
original = Object.instance_method(:load_gem_specifications)
|
|
277
|
+
Object.send(:remove_method, :load_gem_specifications)
|
|
278
|
+
Object.send(:define_method, :load_gem_specifications) { specifications }
|
|
279
|
+
Object.send(:private, :load_gem_specifications)
|
|
280
|
+
yield
|
|
281
|
+
ensure
|
|
282
|
+
Object.send(:remove_method, :load_gem_specifications)
|
|
283
|
+
Object.send(:define_method, :load_gem_specifications, original)
|
|
284
|
+
Object.send(:private, :load_gem_specifications)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def with_stubbed_bundler_methods(methods)
|
|
288
|
+
bundler = Module.new
|
|
289
|
+
methods.each do |name, implementation|
|
|
290
|
+
bundler.define_singleton_method(name, &implementation)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
with_replaced_constant(Object, :Bundler, bundler) do
|
|
294
|
+
yield
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def with_stubbed_platform_methods(methods)
|
|
299
|
+
platform = Module.new
|
|
300
|
+
methods.each do |name, implementation|
|
|
301
|
+
platform.define_singleton_method(name, &implementation)
|
|
302
|
+
end
|
|
303
|
+
platform.const_set(:RUBY, Gem::Platform::RUBY)
|
|
304
|
+
|
|
305
|
+
with_replaced_constant(Gem, :Platform, platform) do
|
|
306
|
+
yield
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def with_replaced_constant(container, name, replacement)
|
|
311
|
+
original = container.const_get(name) if container.const_defined?(name, false)
|
|
312
|
+
container.send(:remove_const, name) if container.const_defined?(name, false)
|
|
313
|
+
container.const_set(name, replacement)
|
|
314
|
+
yield
|
|
315
|
+
ensure
|
|
316
|
+
container.send(:remove_const, name) if container.const_defined?(name, false)
|
|
317
|
+
if original
|
|
318
|
+
container.const_set(name, original)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def platform_round_trips_for_test?(platform_string)
|
|
323
|
+
Gem::Platform.new(platform_string).to_s == platform_string
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def platform_installable_for_test?(spec)
|
|
327
|
+
if Gem::Platform.respond_to?(:installable?)
|
|
328
|
+
Gem::Platform.installable?(spec)
|
|
329
|
+
else
|
|
330
|
+
Gem::Platform.match(spec.platform)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|