test-cmd.rb 1.0.0 → 1.2.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: ceedd530e04b4429280815527f9c18213db595390d6c554854a843c9c9a7717e
4
- data.tar.gz: 98ad59766cd5aa84d4fbed39ca8d2465fd6be4fa3d7895707f6341fbad37e6c4
3
+ metadata.gz: 6c1063e0ddb4b42759986c6772953a164f09c73c440d172d135cbc285a28bad3
4
+ data.tar.gz: 5a77c90c1f3d11686dcd3d3ce50b228520122a0aa329b82ad4bb563ed69d2174
5
5
  SHA512:
6
- metadata.gz: f25e60111e111074f5dd1cdba6ba10f7074a5a2fbe9ddbf57ef08a3d1ecc1096e4119331c98ab74736b689aedf8c5f4b784cd18db7255bb7df01de314a1b1dd5
7
- data.tar.gz: 368188e8228ea2974e2db19d284efabb70ce3a0a2c773d73e372078a82034fa93e69ff9e586c62fc570b49039f4bc759d1b0da1cf8e240ac7f3b631893d5dff3
6
+ metadata.gz: f30023b4013ac900ef6cf1f9c5aa91cace6f796dd08d810c6d9c4bca340bda97490025b379972ed5e37ee23f5edb41f0fc70008c1837fa0af6b2557f4e0cb29e
7
+ data.tar.gz: 874f6f8b2fb5b61d8d222f6e7737459a47c1bd94ec05f4cee35da718c9d463a4530ada6f1711d6d9cc96e54ca26156583d8f82eeb60642f15e05db0a76da088f
data/Rakefile.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "bundler/setup"
2
+ require "bundler/gem_tasks"
2
3
  require "rake/testtask"
3
4
 
4
5
  Rake::TestTask.new do |t|
data/lib/test/cmd.rb CHANGED
@@ -49,8 +49,36 @@ class Test::Cmd
49
49
  tap do
50
50
  @spawned = true
51
51
  @out, @err = Pipe.pair, Pipe.pair
52
- produce(@out, @err)
52
+ ##
53
+ # Spawn in the calling thread so the command's fds are
54
+ # wired up before the reader thread starts. We then close
55
+ # our own copies of the write ends so the reader thread
56
+ # observes EOF the moment the child exits, and reads both
57
+ # streams with whole-buffer reads rather than a byte at
58
+ # a time.
59
+ @pid = Process.spawn(
60
+ @cmd, *@argv,
61
+ {out: @out.w, err: @err.w, in: IO::NULL}
62
+ )
63
+ @out.w.close
64
+ @err.w.close
65
+ @producer = Thread.new do
66
+ @stdout = @out.r.read
67
+ @stderr = @err.r.read
68
+ Process.wait
69
+ @status = $?
70
+ ensure
71
+ @out.r.close
72
+ @err.r.close
73
+ end
53
74
  end
75
+ rescue Errno::ENOENT => ex
76
+ @stderr = ex.message
77
+ @enoent = true
78
+ ##
79
+ # Capture a real non-zero status so predicates like
80
+ # #success? work even though the command never spawned.
81
+ @status = Process.waitpid2(Process.spawn("false")).last
54
82
  end
55
83
 
56
84
  ##
@@ -58,7 +86,7 @@ class Test::Cmd
58
86
  # Returns the status of a process
59
87
  def status
60
88
  spawn
61
- consume(@producer, @out, @err)
89
+ consume
62
90
  @status
63
91
  end
64
92
 
@@ -85,7 +113,7 @@ class Test::Cmd
85
113
  # Returns the contents of stdout
86
114
  def stdout
87
115
  spawn
88
- consume(@producer, @out, @err)
116
+ consume
89
117
  @stdout
90
118
  end
91
119
 
@@ -94,7 +122,7 @@ class Test::Cmd
94
122
  # Returns the contents of stderr
95
123
  def stderr
96
124
  spawn
97
- consume(@producer, @out, @err)
125
+ consume
98
126
  @stderr
99
127
  end
100
128
  # @endgroup
@@ -137,7 +165,7 @@ class Test::Cmd
137
165
  # Returns true when a command can't be found
138
166
  def command_not_found?
139
167
  spawn
140
- consume(@producer, @out, @err)
168
+ consume
141
169
  @enoent
142
170
  end
143
171
  alias_method :not_found?, :command_not_found?
@@ -150,14 +178,14 @@ class Test::Cmd
150
178
  # @yieldparam [Test::Cmd] cmd
151
179
  # Yields an instance of {Test::Cmd Test::Cmd}
152
180
  # @example
153
- # cmd("ruby", "-e", "exit 0")
154
- # .success { print "Command [#{_1.pid}] exited successfully", "\n" }
155
- # .failure { }
181
+ # cmd("ruby", "-e", "exit 0").success do
182
+ # print "ok pid #{_1.pid}", "\n"
183
+ # end
156
184
  # @return [Test::Cmd]
157
185
  def success
158
186
  tap do
159
187
  spawn
160
- consume(@producer, @out, @err)
188
+ consume
161
189
  status.success? ? yield(self) : nil
162
190
  end
163
191
  end
@@ -166,14 +194,14 @@ class Test::Cmd
166
194
  # @yieldparam [Test::Cmd] cmd
167
195
  # Yields an instance of {Test::Cmd Test::Cmd}
168
196
  # @example
169
- # cmd("ruby", "-e", "exit 1")
170
- # .success { }
171
- # .failure { print "Command [#{_1.pid}] exited unsuccessfully", "\n" }
197
+ # cmd("ruby", "-e", "exit 1").failure do
198
+ # print "fail pid #{_1.pid}", "\n"
199
+ # end
172
200
  # @return [Test::Cmd]
173
201
  def failure
174
202
  tap do
175
203
  spawn
176
- consume(@producer, @out, @err)
204
+ consume
177
205
  status.success? ? nil : yield(self)
178
206
  end
179
207
  end
@@ -182,44 +210,13 @@ class Test::Cmd
182
210
  private
183
211
 
184
212
  ##
185
- # @param [Test::Cmd::Pipe] out
186
- # A pipe for stdout
187
- # @param [Test::Cmd::Pipe] err
188
- # A pipe for stderr
189
- # @return [Thread]
190
- # Returns a thread for a spawned command
191
- def produce(out, err)
192
- @producer = Thread.new do
193
- @pid = Process.spawn(@cmd, *@argv, {out: out.w, err: err.w})
194
- Process.wait
195
- @status = $?
196
- rescue Errno::ENOENT => ex
197
- @cmd, @argv, @stderr = "false", [], ex.message
198
- @enoent = true
199
- retry
200
- end
201
- end
202
-
203
- ##
204
- # @param [Thread] thread
205
- # A thread for a spawned command
206
- # @param [Test::Cmd::Pipe] out
207
- # A pipe for stdout
208
- # @param [Test::Cmd::Pipe] err
209
- # A pipe for stderr
213
+ # Blocks until the spawned command has finished, its
214
+ # output has been read, and its exit status captured.
215
+ # Safe to call more than once.
210
216
  # @return [void]
211
- def consume(thread, out, err)
212
- return if @consumed
213
- sleep 0.01 while thread.alive?
214
- loop do
215
- io, _ = IO.select([out.r, err.r], nil, nil, 0.01)
216
- io&.include?(out.r) ? @stdout << out.r.read(1) : nil
217
- io&.include?(err.r) ? @stderr << err.r.read(1) : nil
218
- break unless thread.alive? || IO.select([out.r, err.r], nil, nil, 0.01)
219
- end
220
- ensure
221
- [out, err].each(&:close)
222
- @consumed = true
217
+ def consume
218
+ return unless @producer&.alive?
219
+ @producer.join
223
220
  end
224
221
  end
225
222
 
data/test-cmd.rb.gemspec CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "test-cmd.rb"
5
- gem.authors = ["0x1eef"]
6
- gem.email = ["0x1eef@protonmail.com"]
7
- gem.homepage = "https://github.com/1robertrb/test-cmd.rb#readme"
8
- gem.version = "1.0.0"
5
+ gem.authors = ["robert"]
6
+ gem.email = ["robert@r.uby.dev"]
7
+ gem.homepage = "https://github.com/0x1eef/test-cmd.rb#readme"
8
+ gem.version = "1.2.0"
9
9
  gem.required_ruby_version = ">= 3.0"
10
10
  gem.licenses = ["0BSD"]
11
11
  gem.files = `git ls-files`.split($/)
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-cmd.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - '0x1eef'
7
+ - robert
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -81,7 +81,7 @@ dependencies:
81
81
  version: '13.1'
82
82
  description: test-cmd.rb provides an object-oriented interface for spawning a command
83
83
  email:
84
- - 0x1eef@protonmail.com
84
+ - robert@r.uby.dev
85
85
  executables: []
86
86
  extensions: []
87
87
  extra_rdoc_files: []
@@ -99,7 +99,7 @@ files:
99
99
  - test-cmd.rb.gemspec
100
100
  - test/cmd_test.rb
101
101
  - test/setup.rb
102
- homepage: https://github.com/1robertrb/test-cmd.rb#readme
102
+ homepage: https://github.com/0x1eef/test-cmd.rb#readme
103
103
  licenses:
104
104
  - 0BSD
105
105
  metadata: {}
@@ -117,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0'
119
119
  requirements: []
120
- rubygems_version: 4.0.6
120
+ rubygems_version: 4.0.16
121
121
  specification_version: 4
122
122
  summary: test-cmd.rb provides an object-oriented interface for spawning a command
123
123
  test_files: []