net-ping 2.0.9 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3139e0db447950138a58ab9e60996ef49a89fc89e157ef1b66bf67ef5557450
4
- data.tar.gz: 7529e34db8bf6d258a830fd725969af4dd6eee3ee59a500423e1f13256372079
3
+ metadata.gz: 27c470dd18b95b9b05e68e1dd6e79da70a8c8d4eaaa28581e770fa4d0d028aca
4
+ data.tar.gz: fc020ad255eb094dd057f552e67c36f83a00e87aa88e12837ceb3f2bf1c8673b
5
5
  SHA512:
6
- metadata.gz: '0947a086f3da9bb83a81f16e84d4e5fa761508f6a71109051e2ec315c972c83806085863a67d4cc5299e7c337a8ecef9f4bf8aa4afd907064bbd589dc9ff7966'
7
- data.tar.gz: d16d032dcea9783d50305592c6dd4242436a1357f73233b2af787fafc68a03c6d408f5c1d3b328b2cc62395a7f7f64acd8ca4915fee647154fbb25b0474f98eb
6
+ metadata.gz: 4fc2c4d695cbdb27ddd478b92929f6f9164eb07d768b56ee8f6d69cb102ded9bfe435ff0fd389136b77a86f200440da26f6078bca87561275f881f5b5abb7c24
7
+ data.tar.gz: 5c45a5a55df135b3145e6aab5b3286c27d92487e9a5ac0329ada3641659acef7a8c69a4fb8f22ac2d04b6c68627a6335fd7c9ca2c681987c35713da78472cdbe
data/CHANGES CHANGED
@@ -1,3 +1,19 @@
1
+ == 2.1.0 - 08-Aug-2026
2
+ * Publish platform-specific gem metadata so Linux installs cap2 and current
3
+ Windows RubyInstaller installs win32-security for ICMP support
4
+ [#44](https://github.com/eitoball/net-ping/pull/44).
5
+ * Fix the FreeBSD ping6 timeout option
6
+ [#43](https://github.com/eitoball/net-ping/pull/43).
7
+ * Add Ruby 4.0 to the test matrix and add win32ole as an explicit Windows
8
+ dependency, since it is no longer a default gem as of Ruby 4.0
9
+ [#41](https://github.com/eitoball/net-ping/pull/41).
10
+ * Move Net::Ping::VERSION into its own file so the gemspec version can no
11
+ longer drift out of sync with it
12
+ [#37](https://github.com/eitoball/net-ping/pull/37).
13
+ * Fix `Net::Ping::External#ping6` setting a spurious "undefined method '=~'
14
+ for false" exception on success
15
+ [#39](https://github.com/eitoball/net-ping/pull/39).
16
+
1
17
  == 2.0.9 - 25-Jul-2026
2
18
  * Add GitHub Actions CI [#33](https://github.com/eitoball/net-ping/pull/33)
3
19
  and update its supported Ruby versions [#38](https://github.com/eitoball/net-ping/pull/38).
data/CLAUDE.md ADDED
@@ -0,0 +1,89 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Development commands
6
+
7
+ Install dependencies:
8
+
9
+ ```sh
10
+ bundle install
11
+ ```
12
+
13
+ Run the default suite:
14
+
15
+ ```sh
16
+ bundle exec rake test
17
+ ```
18
+
19
+ Run one protocol-specific test file through its Rake task:
20
+
21
+ ```sh
22
+ bundle exec rake test:tcp
23
+ bundle exec rake test:http
24
+ bundle exec rake test:udp
25
+ bundle exec rake test:external
26
+ bundle exec rake test:icmp
27
+ bundle exec rake test:wmi
28
+ ```
29
+
30
+ There is no lint task or linter configuration. Build the gem with
31
+ `bundle exec rake gem:create`; `bundle exec rake gem:install` builds and
32
+ installs it locally.
33
+
34
+ `test:icmp` requires elevated privileges: root or `net_raw` capability on
35
+ Unix (checked via the `cap2` gem, falling back to a root check if `cap2`
36
+ isn't available), or elevated security on Windows (via `win32-security`).
37
+ The aggregate `test` task (`test/test_net_ping.rb`) only requires the ICMP
38
+ test file when that privilege check passes. External-ping tests invoke the
39
+ system `ping` command; HTTP tests use FakeWeb/webmock except the timeout
40
+ case, which makes a real network connection.
41
+
42
+ CI (`.github/workflows/test.yml`) runs `bundle exec rake test` on
43
+ ubuntu-latest and windows-latest across Ruby 2.7-3.4.
44
+
45
+ ## Architecture
46
+
47
+ This is a Ruby gem that exposes `Net::Ping` implementations for several
48
+ reachability mechanisms:
49
+
50
+ - `lib/net/ping/ping.rb` defines the abstract `Net::Ping` base class and its
51
+ shared state: `host`, `port`, `timeout`, `exception`, `warning`, and
52
+ `duration`.
53
+ - `lib/net/ping/{tcp,udp,icmp,external,http}.rb` implement socket, raw ICMP,
54
+ system-command, and HTTP pings. `wmi.rb` is loaded only on Windows
55
+ (`File::ALT_SEPARATOR` check).
56
+ - `lib/net/ping.rb` is the all-in-one entry point (`require 'net/ping'`);
57
+ requiring a protocol file directly (e.g. `require 'net/ping/tcp'`) loads
58
+ only that implementation and reduces memory footprint.
59
+ - Each protocol has a matching `test/test_net_ping_<protocol>.rb` file. The
60
+ aggregate `test/test_net_ping.rb` requires the supported protocol suites
61
+ based on the platform/privilege checks described above.
62
+
63
+ ## Repository-specific conventions
64
+
65
+ - A subclass `ping` method calls `super(host)` first. The base implementation
66
+ validates that a host is present and resets `@exception`, `@warning`, and
67
+ `@duration` for each attempt.
68
+ - `ping` returns a truthy success value or `false`; callers inspect
69
+ `exception` after failures rather than receive connection errors. Set
70
+ `duration` only after a successful ping and leave it `nil` on failure.
71
+ `ping?` is the boolean wrapper supplied by the base class (or an explicit
72
+ alias in `External`).
73
+ - TCP and UDP treat connection refusal according to their class-level
74
+ `service_check` setting (aliased as `econnrefused`/`ecr`). Keep the
75
+ Boolean-only setter validation and test both modes when changing that
76
+ behavior.
77
+ - Preserve platform branches: `External` maps `ping`/`ping6` options to each
78
+ OS's `ping`/`ping6` syntax (Linux, AIX, BSD/macOS, Solaris, HP-UX, Windows),
79
+ ICMP requires raw-socket privileges, and WMI is Windows-only.
80
+ - HTTP tests stub requests with FakeWeb/webmock and reset proxy environment
81
+ variables in `setup`; register any new HTTP cases rather than making
82
+ ordinary tests depend on live services.
83
+
84
+ ## Repository layout notes
85
+
86
+ - `.worktrees/` contains git worktrees for in-progress branches; treat them
87
+ as separate checkouts, not part of the main tree.
88
+ - `docs/superpowers/` holds plan/spec documents from prior sessions using the
89
+ `superpowers` skill set — reference material, not source.
data/Gemfile CHANGED
@@ -1,2 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
+ gem 'win32-security', '>= 0.2.0' if Gem.win_platform?
4
+
5
+ # No longer a default gem as of Ruby 4.0. Used by the WMI ping class.
6
+ gem 'win32ole', '>= 1.8.8' if Gem.win_platform?
7
+
8
+ gem 'cap2', '>= 0.2.2' if RUBY_PLATFORM =~ /linux/i
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- net-ping (2.0.9)
4
+ net-ping (2.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -3,7 +3,9 @@ A collection of classes that provide different ways to ping computers.
3
3
 
4
4
  ## Prerequisites
5
5
  * ffi
6
- * win32-security (MS Windows only)
6
+ * win32-security (installed from the Windows gem artifact)
7
+ * win32ole (installed from the Windows gem artifact)
8
+ * cap2 (installed from the Linux gem artifact)
7
9
  * webmock (test only)
8
10
  * test-unit (test only)
9
11
 
data/Rakefile CHANGED
@@ -5,25 +5,110 @@ include Object.const_defined?(:RbConfig) ? RbConfig : Config
5
5
 
6
6
  CLEAN.include("**/*.gem", "**/*.rbc")
7
7
 
8
+ GEMSPEC_FILES = %w[
9
+ net-ping.gemspec
10
+ net-ping-universal-linux.gemspec
11
+ net-ping-universal-mingw-ucrt.gemspec
12
+ ].freeze
13
+
14
+ EXPECTED_GEM_METADATA = {
15
+ 'net-ping.gemspec' => ['ruby', {}],
16
+ 'net-ping-universal-linux.gemspec' => ['universal-linux', {'cap2' => '>= 0.2.2'}],
17
+ 'net-ping-universal-mingw-ucrt.gemspec' => [
18
+ 'universal-mingw-ucrt',
19
+ {'win32-security' => '>= 0.2.0', 'win32ole' => '>= 1.8.8'}
20
+ ]
21
+ }.freeze
22
+
23
+ def load_gem_specifications
24
+ GEMSPEC_FILES.map do |path|
25
+ spec = Gem::Specification.load(path)
26
+ abort("Unable to load #{path}") unless spec
27
+ spec
28
+ end
29
+ end
30
+
31
+ def with_unbundled_env
32
+ if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
33
+ Bundler.with_unbundled_env { yield }
34
+ elsif defined?(Bundler) && Bundler.respond_to?(:with_clean_env)
35
+ Bundler.with_clean_env { yield }
36
+ else
37
+ yield
38
+ end
39
+ end
40
+
41
+ def platform_round_trips?(platform_string)
42
+ Gem::Platform.new(platform_string).to_s == platform_string
43
+ end
44
+
45
+ def platform_installable?(spec)
46
+ if Gem::Platform.respond_to?(:installable?)
47
+ Gem::Platform.installable?(spec)
48
+ else
49
+ Gem::Platform.match(spec.platform)
50
+ end
51
+ end
52
+
53
+ def select_install_specification(specifications = load_gem_specifications)
54
+ specific = specifications.find do |spec|
55
+ spec.platform != Gem::Platform::RUBY && platform_installable?(spec)
56
+ end
57
+ spec = specific || specifications.find { |item| item.platform == Gem::Platform::RUBY }
58
+ abort('Unable to find a Ruby fallback gem specification') unless spec
59
+ spec
60
+ end
61
+
8
62
  namespace 'gem' do
9
- desc 'Create the net-ping gem'
63
+ desc 'Create the net-ping gem artifacts (ruby, universal-linux, universal-mingw-ucrt)'
10
64
  task :create => [:clean] do
11
- spec = eval(IO.read('net-ping.gemspec'))
65
+ specifications = load_gem_specifications
12
66
  if Gem::VERSION.to_f < 2.0
13
- Gem::Builder.new(spec).build
67
+ specifications.each do |spec|
68
+ Gem::Builder.new(spec).build
69
+ end
14
70
  else
15
71
  require 'rubygems/package'
16
- Gem::Package.build(spec)
72
+ specifications.each do |spec|
73
+ Gem::Package.build(spec)
74
+ end
75
+ end
76
+ end
77
+
78
+ desc 'Validate the net-ping gem artifacts'
79
+ task :check do
80
+ require 'rubygems/package'
81
+
82
+ EXPECTED_GEM_METADATA.each do |path, expectation|
83
+ expected_platform, expected_dependencies = expectation
84
+ artifact = Gem::Specification.load(path).file_name
85
+ packaged_spec = Gem::Package.new(artifact).spec
86
+ actual_dependencies = packaged_spec.runtime_dependencies.each_with_object({}) do |dependency, memo|
87
+ memo[dependency.name] = dependency.requirement.to_s
88
+ end
89
+
90
+ if platform_round_trips?(expected_platform) && packaged_spec.platform.to_s != expected_platform
91
+ abort("#{artifact}: expected platform #{expected_platform.inspect}, got #{packaged_spec.platform.to_s.inspect}")
92
+ end
93
+
94
+ if actual_dependencies != expected_dependencies
95
+ abort("#{artifact}: expected dependencies #{expected_dependencies.inspect}, got #{actual_dependencies.inspect}")
96
+ end
17
97
  end
18
98
  end
19
99
 
20
100
  desc 'Install the net-ping gem'
21
101
  task :install => [:create] do
22
- gem_file = Dir["*.gem"].first
23
- if RUBY_PLATFORM == 'java'
24
- sh "jruby -S gem install -l #{gem_file}"
102
+ spec = select_install_specification
103
+
104
+ install_command = if RUBY_PLATFORM == 'java'
105
+ ['jruby', '-S', 'gem', 'install', '-l', spec.file_name]
25
106
  else
26
- sh "gem install -l #{gem_file}"
107
+ ['gem', 'install', '-l', spec.file_name]
108
+ end
109
+
110
+ with_unbundled_env do
111
+ sh(*install_command)
27
112
  end
28
113
  end
29
114
  end
@@ -0,0 +1,317 @@
1
+ # Platform-specific Gem Dependencies Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Build and validate RubyGems artifacts that install `cap2` on Linux and `win32-security` on current Windows RubyInstaller systems.
6
+
7
+ **Architecture:** Keep `net-ping.gemspec` OS-independent and create two small gemspec overlays for Linux and `mingw-ucrt`. Centralize artifact generation and metadata validation in the Rake gem namespace, then run that validation in the existing Linux/Windows CI matrix.
8
+
9
+ **Tech Stack:** Ruby, RubyGems (`Gem::Specification`, `Gem::Package`, `Gem::Platform`), Rake, test-unit, GitHub Actions.
10
+
11
+ ## Global Constraints
12
+
13
+ - Produce exactly three same-version `net-ping` gems: `ruby`, `universal-linux`, and `universal-mingw-ucrt`.
14
+ - `universal-linux` must add `cap2 (>= 0.2.2)`; `universal-mingw-ucrt` must add `win32-security (>= 0.2.0)`.
15
+ - The Ruby gem must not contain either OS-specific runtime dependency.
16
+ - Support `mingw-ucrt` only; legacy `mingw32` is out of scope.
17
+ - CI builds and validates artifacts but never publishes them.
18
+ - Do not create git commits for this work.
19
+
20
+ ---
21
+
22
+ ## File Structure
23
+
24
+ | File | Responsibility |
25
+ | --- | --- |
26
+ | `net-ping.gemspec` | Define the OS-independent Ruby artifact and common metadata. |
27
+ | `net-ping-universal-linux.gemspec` | Overlay `universal-linux` and the `cap2` runtime dependency. |
28
+ | `net-ping-universal-mingw-ucrt.gemspec` | Overlay `universal-mingw-ucrt` and the `win32-security` runtime dependency. |
29
+ | `Rakefile` | Build, inspect, and locally install the platform-appropriate artifact. |
30
+ | `test/test_net_ping_gem_packaging.rb` | Build temporary artifacts and assert their platform/dependency metadata. |
31
+ | `test/test_net_ping.rb` | Load the packaging test in the aggregate suite. |
32
+ | `.github/workflows/test.yml` | Run gem artifact checks on Linux and Windows. |
33
+ | `README.md` | Explain automatic platform-specific dependency installation. |
34
+ | `CHANGES` | Record the metadata correction for the next release. |
35
+
36
+ ### Task 1: Define and test platform gem specifications
37
+
38
+ **Files:**
39
+ - Create: `net-ping-universal-linux.gemspec`
40
+ - Create: `net-ping-universal-mingw-ucrt.gemspec`
41
+ - Create: `test/test_net_ping_gem_packaging.rb`
42
+ - Modify: `net-ping.gemspec:1-40`
43
+ - Modify: `test/test_net_ping.rb`
44
+
45
+ **Interfaces:**
46
+ - Consumes: `net-ping.gemspec`, which supplies the shared `Gem::Specification`.
47
+ - Produces: three loadable gemspecs whose `platform` and `runtime_dependencies` describe their target platform.
48
+
49
+ - [ ] **Step 1: Write the failing package-metadata test**
50
+
51
+ Create `test/test_net_ping_gem_packaging.rb`. Load every gemspec before building any artifact so `spec.files` cannot include an artifact created for an earlier specification. Build the loaded specifications into temporary `.gem` files, open each using `Gem::Package`, and assert the platform and runtime dependencies:
52
+
53
+ ```ruby
54
+ require 'rubygems/package'
55
+ require 'test/unit'
56
+
57
+ class TestNetPingGemPackaging < Test::Unit::TestCase
58
+ EXPECTATIONS = {
59
+ 'net-ping.gemspec' => ['ruby', {}],
60
+ 'net-ping-universal-linux.gemspec' => ['universal-linux', {'cap2' => '>= 0.2.2'}],
61
+ 'net-ping-universal-mingw-ucrt.gemspec' => [
62
+ 'universal-mingw-ucrt',
63
+ {'win32-security' => '>= 0.2.0'}
64
+ ]
65
+ }.freeze
66
+
67
+ def setup
68
+ @specifications = EXPECTATIONS.keys.map do |path|
69
+ [path, Gem::Specification.load(path)]
70
+ end
71
+ @gem_files = @specifications.map { |_, spec| Gem::Package.build(spec) }
72
+ end
73
+
74
+ def teardown
75
+ @gem_files.each { |path| File.delete(path) if File.exist?(path) }
76
+ end
77
+
78
+ def test_platforms_and_runtime_dependencies
79
+ @specifications.each do |path, spec|
80
+ expected_platform, expected_dependencies = EXPECTATIONS.fetch(path)
81
+ packaged_spec = Gem::Package.new(spec.file_name).spec
82
+ dependencies = packaged_spec.runtime_dependencies.to_h do |dependency|
83
+ [dependency.name, dependency.requirement.to_s]
84
+ end
85
+
86
+ assert_equal(expected_platform, packaged_spec.platform.to_s, path)
87
+ assert_equal(expected_dependencies, dependencies, path)
88
+ end
89
+ end
90
+ end
91
+ ```
92
+
93
+ Add `require 'test_net_ping_gem_packaging'` to `test/test_net_ping.rb`.
94
+
95
+ - [ ] **Step 2: Run the new test to verify it fails**
96
+
97
+ Run:
98
+
99
+ ```sh
100
+ bundle exec ruby -Itest test/test_net_ping_gem_packaging.rb
101
+ ```
102
+
103
+ Expected: failure because the Linux and Windows overlay gemspec files do not exist.
104
+
105
+ - [ ] **Step 3: Make the base gemspec OS-independent and add overlays**
106
+
107
+ Remove the `File::ALT_SEPARATOR`, `RbConfig`, and `RUBY_PLATFORM` conditional runtime dependency logic from `net-ping.gemspec`; leave only shared metadata and development dependencies.
108
+
109
+ Create `net-ping-universal-linux.gemspec`:
110
+
111
+ ```ruby
112
+ spec = Gem::Specification.load('net-ping.gemspec')
113
+ spec.platform = Gem::Platform.new(['universal', 'linux'])
114
+ spec.add_dependency('cap2', '>= 0.2.2')
115
+ spec
116
+ ```
117
+
118
+ Create `net-ping-universal-mingw-ucrt.gemspec`:
119
+
120
+ ```ruby
121
+ spec = Gem::Specification.load('net-ping.gemspec')
122
+ spec.platform = Gem::Platform.new(['universal', 'mingw-ucrt'])
123
+ spec.add_dependency('win32-security', '>= 0.2.0')
124
+ spec
125
+ ```
126
+
127
+ Use the project’s existing block-style `Gem::Specification.new` format for the base gemspec. Verify the installed RubyGems version accepts the two platform strings with `Gem::Platform.new`.
128
+
129
+ - [ ] **Step 4: Run the package-metadata test to verify it passes**
130
+
131
+ Run:
132
+
133
+ ```sh
134
+ bundle exec ruby -Itest test/test_net_ping_gem_packaging.rb
135
+ ```
136
+
137
+ Expected: PASS; all three artifacts have the expected platform and exactly the expected runtime dependencies.
138
+
139
+ - [ ] **Step 5: Run the aggregate test suite**
140
+
141
+ Run:
142
+
143
+ ```sh
144
+ bundle exec rake test
145
+ ```
146
+
147
+ Expected: PASS.
148
+
149
+ ### Task 2: Make Rake build, validate, and install platform artifacts deterministically
150
+
151
+ **Files:**
152
+ - Modify: `Rakefile:6-29`
153
+
154
+ **Interfaces:**
155
+ - Consumes: the three gemspec paths from Task 1.
156
+ - Produces: `gem:create` builds all artifacts; `gem:check` aborts on invalid artifact metadata; `gem:install` installs the local platform’s specific artifact or the Ruby fallback.
157
+
158
+ - [ ] **Step 1: Add a failing artifact-validation command**
159
+
160
+ Add `gem:check` to the `gem` namespace. It must load the three expected gem files with `Gem::Package`, map each runtime dependency to `name => requirement.to_s`, and abort if the platform or dependency hash differs from:
161
+
162
+ ```ruby
163
+ {
164
+ 'ruby' => {},
165
+ 'universal-linux' => {'cap2' => '>= 0.2.2'},
166
+ 'universal-mingw-ucrt' => {'win32-security' => '>= 0.2.0'}
167
+ }
168
+ ```
169
+
170
+ Run it before changing `gem:create`:
171
+
172
+ ```sh
173
+ bundle exec rake clean gem:create gem:check
174
+ ```
175
+
176
+ Expected: FAIL because `gem:create` still builds only one artifact.
177
+
178
+ - [ ] **Step 2: Implement deterministic specification loading and artifact creation**
179
+
180
+ Define the shared list once in `Rakefile`:
181
+
182
+ ```ruby
183
+ GEMSPEC_FILES = %w[
184
+ net-ping.gemspec
185
+ net-ping-universal-linux.gemspec
186
+ net-ping-universal-mingw-ucrt.gemspec
187
+ ].freeze
188
+ ```
189
+
190
+ Update `gem:create` to load all files before it builds any artifact:
191
+
192
+ ```ruby
193
+ specifications = GEMSPEC_FILES.map { |path| Gem::Specification.load(path) }
194
+ specifications.each { |spec| Gem::Package.build(spec) }
195
+ ```
196
+
197
+ Retain the existing RubyGems pre-2.0 builder branch if support for it is still required. In either branch, load every specification before beginning the build loop.
198
+
199
+ Implement `gem:check` using `Gem::Package.new(file).spec`; use `abort` with the artifact filename and expected/actual values when a check fails.
200
+
201
+ Update `gem:install` to load the same specification list and select:
202
+
203
+ ```ruby
204
+ specific = specifications.find do |spec|
205
+ spec.platform != Gem::Platform::RUBY && Gem::Platform.installable?(spec)
206
+ end
207
+ spec = specific || specifications.find { |item| item.platform == Gem::Platform::RUBY }
208
+ ```
209
+
210
+ Install `spec.file_name`, not `Dir['*.gem'].first`; abort when no Ruby fallback specification is found.
211
+
212
+ - [ ] **Step 3: Run artifact validation to verify it passes**
213
+
214
+ Run:
215
+
216
+ ```sh
217
+ bundle exec rake gem:create gem:check
218
+ ```
219
+
220
+ Expected: PASS and creation of `net-ping-<version>.gem`,
221
+ `net-ping-<version>-universal-linux.gem`, and
222
+ `net-ping-<version>-universal-mingw-ucrt.gem`.
223
+
224
+ - [ ] **Step 4: Verify local artifact selection**
225
+
226
+ Run:
227
+
228
+ ```sh
229
+ bundle exec rake gem:install
230
+ gem specification net-ping platform
231
+ ```
232
+
233
+ Expected on Linux: `universal-linux`; on Windows: `universal-mingw-ucrt`; on
234
+ other platforms: `ruby`.
235
+
236
+ - [ ] **Step 5: Re-run tests**
237
+
238
+ Run:
239
+
240
+ ```sh
241
+ bundle exec rake test
242
+ ```
243
+
244
+ Expected: PASS.
245
+
246
+ ### Task 3: Enforce packaging in CI and document the behavior
247
+
248
+ **Files:**
249
+ - Modify: `.github/workflows/test.yml:19-28`
250
+ - Modify: `README.md:4-15`
251
+ - Modify: `CHANGES:1-13`
252
+
253
+ **Interfaces:**
254
+ - Consumes: `gem:create` and `gem:check` from Task 2.
255
+ - Produces: CI verification on both matrix operating systems and accurate end-user dependency documentation.
256
+
257
+ - [ ] **Step 1: Add the CI packaging verification step**
258
+
259
+ After the existing `bundle exec rake test` step, add:
260
+
261
+ ```yaml
262
+ - run: bundle exec rake gem:create gem:check
263
+ ```
264
+
265
+ Do not add RubyGems credentials, publishing actions, or tag triggers.
266
+
267
+ - [ ] **Step 2: Document platform-specific dependency resolution**
268
+
269
+ Replace the prerequisite list’s unconditional `win32-security (MS Windows
270
+ only)` wording with text that states RubyGems installs `win32-security` from
271
+ the Windows artifact and `cap2` from the Linux artifact when installing
272
+ `net-ping`. Keep the installation command `gem install net-ping`.
273
+
274
+ At the beginning of `CHANGES`, add:
275
+
276
+ ```text
277
+ == Next Release
278
+ * Publish platform-specific gem metadata so Linux installs cap2 and current
279
+ Windows RubyInstaller installs win32-security for ICMP support.
280
+ ```
281
+
282
+ - [ ] **Step 3: Run targeted packaging and full test verification**
283
+
284
+ Run:
285
+
286
+ ```sh
287
+ bundle exec rake gem:create gem:check
288
+ bundle exec rake test
289
+ ```
290
+
291
+ Expected: both commands PASS.
292
+
293
+ - [ ] **Step 4: Inspect the final diff without committing**
294
+
295
+ Run:
296
+
297
+ ```sh
298
+ git diff --check
299
+ git diff -- net-ping.gemspec net-ping-universal-linux.gemspec \
300
+ net-ping-universal-mingw-ucrt.gemspec Rakefile \
301
+ test/test_net_ping_gem_packaging.rb test/test_net_ping.rb \
302
+ .github/workflows/test.yml README.md CHANGES
303
+ ```
304
+
305
+ Expected: only the planned packaging, CI, test, and documentation changes;
306
+ no whitespace errors and no git commit.
307
+
308
+ ## Plan Self-Review
309
+
310
+ - **Spec coverage:** Task 1 creates the three required artifacts and assigns
311
+ their dependencies; Task 2 validates metadata and selects a local artifact;
312
+ Task 3 runs validation in CI and documents the behavior. CI publishing,
313
+ legacy `mingw32`, and runtime protocol changes are excluded.
314
+ - **Placeholder scan:** No unresolved item, unspecified test, or ambiguous
315
+ implementation step remains.
316
+ - **Consistency:** The gemspec filenames, platform names, dependency
317
+ versions, and Rake task names are identical across all tasks.
@@ -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.
@@ -66,7 +66,7 @@ module Net
66
66
  bool = true # Success, at least one response.
67
67
  end
68
68
 
69
- if err & (err =~ /warning/i)
69
+ if err && (err =~ /warning/i)
70
70
  @warning = err.chomp
71
71
  end
72
72
  when 2
@@ -116,6 +116,8 @@ 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
122
  pcmd += ['-c', count.to_s, '-i', interval.to_s, host]
121
123
  when /solaris|sunos/i
@@ -147,7 +149,7 @@ module Net
147
149
  bool = true # Success, at least one response.
148
150
  end
149
151
 
150
- if err & err =~ /warning/i
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 = '2.0.9'
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,6 @@
1
+ module Net
2
+ class Ping
3
+ # The version of the net-ping library.
4
+ VERSION = '2.1.0'
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ spec = Gem::Specification.load('net-ping.gemspec').dup
2
+ spec.platform = Gem::Platform.new(['universal', 'linux'])
3
+ spec.add_dependency('cap2', '>= 0.2.2')
4
+ spec
@@ -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,15 +1,15 @@
1
- require 'rbconfig'
1
+ require_relative "lib/net/ping/version"
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'net-ping'
5
- spec.version = '2.0.9'
5
+ spec.version = Net::Ping::VERSION
6
6
  spec.license = 'Artistic 2.0'
7
7
  spec.author = 'Chris Chernesky'
8
8
  spec.email = 'chris.netping@tinderglow.com'
9
9
  spec.homepage = 'https://github.com/chernesk/net-ping'
10
10
  spec.summary = 'A ping interface for Ruby.'
11
11
  spec.test_file = 'test/test_net_ping.rb'
12
- spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
12
+ spec.files = Dir['**/*'].reject { |f| f.include?('git') || File.extname(f) == '.gem' }
13
13
  spec.metadata = {
14
14
  'bug_tracker_uri' => "#{spec.homepage}/issues",
15
15
  'changelog_uri' => "#{spec.homepage}/blob/master/CHANGES",
@@ -28,17 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency('rake', '>= 0')
29
29
  spec.add_development_dependency('pry-byebug', '>= 0')
30
30
 
31
- if File::ALT_SEPARATOR
32
- require 'rbconfig'
33
- arch = RbConfig::CONFIG['build_os'] || 'mingw32' # JRuby
34
- spec.platform = Gem::Platform.new(['universal', arch])
35
- spec.platform.version = nil
36
-
37
- # Used for icmp pings.
38
- spec.add_dependency('win32-security', '>= 0.2.0')
39
- end
40
- spec.add_dependency('cap2', '>= 0.2.2') if RUBY_PLATFORM =~ /linux/i
41
-
42
31
  spec.description = <<-EOF
43
32
  The net-ping library provides a ping interface for Ruby. It includes
44
33
  separate TCP, HTTP, LDAP, ICMP, UDP, WMI (for Windows) and external ping
@@ -5,6 +5,7 @@
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'
@@ -38,6 +39,6 @@ end
38
39
 
39
40
  class TC_Net_Ping < Test::Unit::TestCase
40
41
  def test_net_ping_version
41
- assert_equal('2.0.9', Net::Ping::VERSION)
42
+ assert_equal(Gem::Specification.load('net-ping.gemspec').version.to_s, Net::Ping::VERSION)
42
43
  end
43
44
  end
@@ -140,6 +140,30 @@ class TC_Net_Ping_External < Test::Unit::TestCase
140
140
  assert_false(@bad.ping?)
141
141
  end
142
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
+
143
167
  test "ping6 uses interval for macOS" do
144
168
  command = nil
145
169
  original_os = RbConfig::CONFIG['host_os']
@@ -164,6 +188,41 @@ class TC_Net_Ping_External < Test::Unit::TestCase
164
188
  Open3.define_singleton_method(:popen3, original_popen3)
165
189
  end
166
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
+
167
226
  def teardown
168
227
  @host = nil
169
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
@@ -131,12 +131,11 @@ class TC_Net_Ping_HTTP < Test::Unit::TestCase
131
131
  assert_equal(5, @bad.timeout)
132
132
  end
133
133
 
134
- # TODO: Figure out how to do this with WebMock.
135
134
  test 'ping fails if timeout exceeded' do
136
- WebMock.allow_net_connect!
135
+ stub_request(:head, 'https://www.google.com/').to_timeout
137
136
  @http = Net::Ping::HTTP.new('https://www.google.com', 443, 0.001)
138
137
  assert_false(@http.ping?)
139
- assert(['Failed to open TCP connection to www.google.com:443 (execution expired)', 'execution expired', 'Net::OpenTimeout'].include?(@http.exception))
138
+ assert_include(['execution expired', 'Net::OpenTimeout'], @http.exception)
140
139
  end
141
140
 
142
141
  test 'exception attribute basic functionality' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-ping
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.9
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Chernesky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-25 00:00:00.000000000 Z
11
+ date: 2026-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-unit
@@ -80,12 +80,15 @@ extra_rdoc_files:
80
80
  files:
81
81
  - CHANGES
82
82
  - CHANGES.out_of_date
83
+ - CLAUDE.md
83
84
  - Gemfile
84
85
  - Gemfile.lock
85
86
  - MANIFEST
86
87
  - README.md
87
88
  - Rakefile
88
89
  - doc/ping.txt
90
+ - docs/superpowers/plans/2026-07-25-platform-specific-gems.md
91
+ - docs/superpowers/specs/2026-07-25-platform-specific-gems-design.md
89
92
  - examples/example_pingexternal.rb
90
93
  - examples/example_pinghttp.rb
91
94
  - examples/example_pingtcp.rb
@@ -97,10 +100,14 @@ files:
97
100
  - lib/net/ping/ping.rb
98
101
  - lib/net/ping/tcp.rb
99
102
  - lib/net/ping/udp.rb
103
+ - lib/net/ping/version.rb
100
104
  - lib/net/ping/wmi.rb
105
+ - net-ping-universal-linux.gemspec
106
+ - net-ping-universal-mingw-ucrt.gemspec
101
107
  - net-ping.gemspec
102
108
  - test/test_net_ping.rb
103
109
  - test/test_net_ping_external.rb
110
+ - test/test_net_ping_gem_packaging.rb
104
111
  - test/test_net_ping_http.rb
105
112
  - test/test_net_ping_icmp.rb
106
113
  - test/test_net_ping_tcp.rb