test-cmd.rb 1.1.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: 050e40eb1971d85c78300e005a7a633b3e56288dfaad75e201e73c4d51a43ecc
4
- data.tar.gz: 7d1fcfd581aa09af2d6159ac809fe40bf4cc68a351026b44c2ceb32d50269e7c
3
+ metadata.gz: 6c1063e0ddb4b42759986c6772953a164f09c73c440d172d135cbc285a28bad3
4
+ data.tar.gz: 5a77c90c1f3d11686dcd3d3ce50b228520122a0aa329b82ad4bb563ed69d2174
5
5
  SHA512:
6
- metadata.gz: 291d1ee4ad90a5d8f10006a62f4662b1a9c95a555677646d5d860055b05c8c78b9b112d3365928fa1ff17389bc1f6020e1ee30f327ba366847a9503122f1bec2
7
- data.tar.gz: 6d965b87d2443829f36916cbbc3192b5a2eb0e53a1cde069fc4f66d803bbd6f70a7208a607e767f685f7939d914bd112754e29773d8647e336d7d4766e4c6ee3
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
@@ -31,7 +31,6 @@ class Test::Cmd
31
31
  @stdout = ""
32
32
  @stderr = ""
33
33
  @enoent = false
34
- @waiter = Queue.new
35
34
  end
36
35
 
37
36
  ##
@@ -50,9 +49,36 @@ class Test::Cmd
50
49
  tap do
51
50
  @spawned = true
52
51
  @out, @err = Pipe.pair, Pipe.pair
53
- produce(@out, @err)
54
- @waiter.pop
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
55
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
56
82
  end
57
83
 
58
84
  ##
@@ -60,7 +86,7 @@ class Test::Cmd
60
86
  # Returns the status of a process
61
87
  def status
62
88
  spawn
63
- consume(@producer, @out, @err)
89
+ consume
64
90
  @status
65
91
  end
66
92
 
@@ -87,7 +113,7 @@ class Test::Cmd
87
113
  # Returns the contents of stdout
88
114
  def stdout
89
115
  spawn
90
- consume(@producer, @out, @err)
116
+ consume
91
117
  @stdout
92
118
  end
93
119
 
@@ -96,7 +122,7 @@ class Test::Cmd
96
122
  # Returns the contents of stderr
97
123
  def stderr
98
124
  spawn
99
- consume(@producer, @out, @err)
125
+ consume
100
126
  @stderr
101
127
  end
102
128
  # @endgroup
@@ -139,7 +165,7 @@ class Test::Cmd
139
165
  # Returns true when a command can't be found
140
166
  def command_not_found?
141
167
  spawn
142
- consume(@producer, @out, @err)
168
+ consume
143
169
  @enoent
144
170
  end
145
171
  alias_method :not_found?, :command_not_found?
@@ -152,14 +178,14 @@ class Test::Cmd
152
178
  # @yieldparam [Test::Cmd] cmd
153
179
  # Yields an instance of {Test::Cmd Test::Cmd}
154
180
  # @example
155
- # cmd("ruby", "-e", "exit 0")
156
- # .success { print "Command [#{_1.pid}] exited successfully", "\n" }
157
- # .failure { }
181
+ # cmd("ruby", "-e", "exit 0").success do
182
+ # print "ok pid #{_1.pid}", "\n"
183
+ # end
158
184
  # @return [Test::Cmd]
159
185
  def success
160
186
  tap do
161
187
  spawn
162
- consume(@producer, @out, @err)
188
+ consume
163
189
  status.success? ? yield(self) : nil
164
190
  end
165
191
  end
@@ -168,14 +194,14 @@ class Test::Cmd
168
194
  # @yieldparam [Test::Cmd] cmd
169
195
  # Yields an instance of {Test::Cmd Test::Cmd}
170
196
  # @example
171
- # cmd("ruby", "-e", "exit 1")
172
- # .success { }
173
- # .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
174
200
  # @return [Test::Cmd]
175
201
  def failure
176
202
  tap do
177
203
  spawn
178
- consume(@producer, @out, @err)
204
+ consume
179
205
  status.success? ? nil : yield(self)
180
206
  end
181
207
  end
@@ -184,45 +210,13 @@ class Test::Cmd
184
210
  private
185
211
 
186
212
  ##
187
- # @param [Test::Cmd::Pipe] out
188
- # A pipe for stdout
189
- # @param [Test::Cmd::Pipe] err
190
- # A pipe for stderr
191
- # @return [Thread]
192
- # Returns a thread for a spawned command
193
- def produce(out, err)
194
- @producer = Thread.new do
195
- @pid = Process.spawn(@cmd, *@argv, {out: out.w, err: err.w})
196
- @waiter.push(nil)
197
- Process.wait
198
- @status = $?
199
- rescue Errno::ENOENT => ex
200
- @cmd, @argv, @stderr = "false", [], ex.message
201
- @enoent = true
202
- retry
203
- end
204
- end
205
-
206
- ##
207
- # @param [Thread] thread
208
- # A thread for a spawned command
209
- # @param [Test::Cmd::Pipe] out
210
- # A pipe for stdout
211
- # @param [Test::Cmd::Pipe] err
212
- # 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.
213
216
  # @return [void]
214
- def consume(thread, out, err)
215
- return if @consumed
216
- sleep 0.01 while thread.alive?
217
- loop do
218
- io, _ = IO.select([out.r, err.r], nil, nil, 0.01)
219
- io&.include?(out.r) ? @stdout << out.r.read(1) : nil
220
- io&.include?(err.r) ? @stderr << err.r.read(1) : nil
221
- break unless thread.alive? || IO.select([out.r, err.r], nil, nil, 0.01)
222
- end
223
- ensure
224
- [out, err].each(&:close)
225
- @consumed = true
217
+ def consume
218
+ return unless @producer&.alive?
219
+ @producer.join
226
220
  end
227
221
  end
228
222
 
data/test-cmd.rb.gemspec CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |gem|
4
4
  gem.name = "test-cmd.rb"
5
5
  gem.authors = ["robert"]
6
6
  gem.email = ["robert@r.uby.dev"]
7
- gem.homepage = "https://github.com/1robertrb/test-cmd.rb#readme"
8
- gem.version = "1.1.0"
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-cmd.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - robert
@@ -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: []