test-cmd.rb 2.0.0 → 2.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: c6c0bc0c0b5e20d51b2455762362ee54301e95c6171a6427dbdf6ea85acdc834
4
- data.tar.gz: 9bcbbe35149e54547b7e01a678de58a068e4a35a0728d454fa613f26d2d9c3ee
3
+ metadata.gz: e57955db2ea9f64997167878e30449b920c1746751e493b54c1ff549b852d55d
4
+ data.tar.gz: 3e2527cb8deedc6205726ff8900aa2e23c15b69c9662debfb9322ebbc38e7ee3
5
5
  SHA512:
6
- metadata.gz: ec35ba65fefe5c978fe9d467d90f1c9719a984078f2bafb22f1a9e1757ca322533ee99c7f77d4268bbb051986230685058da3da88ca2674567b264d915b5f97b
7
- data.tar.gz: 517746b21cb1edd44a3d96216ae3b842b88acec38c6b3fad389d3748ef8cacc241f4baf205aa1fe285a2aea92c5775e359a04d6fd89f5f9f2e205a7e13784bf8
6
+ metadata.gz: 782b41654883c063415e13191a28fdaf719123db4c4980a1f69102d23ad2026a61e8ce0b2c927cde4e12b321426cdcf1ecd25fce528fca5d586fe627160c8373
7
+ data.tar.gz: 442fb73c9759f458c448729a2893b507ab0c8e6db2d5d8bea4d465b2b117c01794f9b501bb910460dbbc9a56f627c8147dea53a7e0814c1ae503133b4cc2774a
data/README.md CHANGED
@@ -36,14 +36,59 @@ A command is created with
36
36
  which takes the name or path of a command, and given additional
37
37
  arguments with
38
38
  [`Test::Command#argv`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#argv-instance_method).
39
+ Environment variables can be set for the spawned process with
40
+ [`Test::Command#env`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#env-instance_method),
41
+ and standard input with
42
+ [`Test::Command#stdin`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#stdin-instance_method).
39
43
  The instance has access to the command's process ID, exit status,
40
44
  standard output stream, and standard error stream.
41
45
 
42
46
  ```ruby
43
47
  require "test-cmd"
44
- Test::Command.new("ls").argv("-l").stdout
48
+ puts Test::Command.new("ls").argv("-l").stdout
49
+ puts Test::Command.new("env").env("FOO" => "bar").stdout
50
+ test_cmd = Test::Command.new("tr", "a-z", "A-Z").stdin("hello").stdout # => "HELLO"
45
51
  ```
46
52
 
53
+ <details>
54
+ <summary>Environment</summary>
55
+ <br>
56
+
57
+ Environment variables for the spawned process are set with
58
+ [`Test::Command#env`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#env-instance_method),
59
+ which merges the given variables into the child's environment and
60
+ returns the command for chaining.
61
+
62
+ ```ruby
63
+ require "test-cmd"
64
+ puts Test::Command
65
+ .new("ruby", "-e", "puts ENV['FOO']")
66
+ .env("FOO" => "42")
67
+ .stdout # => "42\n"
68
+ ```
69
+ </details>
70
+
71
+ <details>
72
+ <summary>Standard input</summary>
73
+ <br>
74
+
75
+ Standard input for the spawned process is set with
76
+ [`Test::Command#stdin`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#stdin-instance_method).
77
+ Pass a String to write it to the child's standard input, or another
78
+ [Test::Command](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html)
79
+ whose standard output will be used as the standard input.
80
+
81
+ ```ruby
82
+ require "test-cmd"
83
+ puts Test::Command.new("cat").stdin("hello world").stdout # => "hello world"
84
+
85
+ # Pipe one command's standard output into another's standard input
86
+ puts Test::Command.new("tr", "a-z", "A-Z")
87
+ .stdin(Test::Command.new("echo", "hello"))
88
+ .stdout # => "HELLO\n"
89
+ ```
90
+ </details>
91
+
47
92
  <details>
48
93
  <summary>Callbacks</summary>
49
94
  <br>
data/lib/test/cmd.rb CHANGED
@@ -26,6 +26,7 @@ class Test::Command
26
26
  def initialize(cmd, *argv)
27
27
  @cmd = cmd
28
28
  @argv = argv.dup
29
+ @env = {}
29
30
  @status = nil
30
31
  @spawned = false
31
32
  @stdout = ""
@@ -41,25 +42,44 @@ class Test::Command
41
42
  tap { @argv.concat(argv) }
42
43
  end
43
44
 
45
+ ##
46
+ # @param [Hash{String => String}] env
47
+ # Environment variables to set for the spawned command
48
+ # @return [Test::Command]
49
+ def env(env)
50
+ tap { @env.merge!(env) }
51
+ end
52
+
53
+ ##
54
+ # Presets the standard input that will be sent to the
55
+ # spawned process. Pass a String, or another
56
+ # {Test::Command Test::Command} whose standard output
57
+ # will be used as the standard input.
58
+ # @param [String, Test::Command] data
59
+ # The standard input of the spawned process
60
+ # @example
61
+ # cmd = Test::Command.new("cat").stdin("hello world")
62
+ # puts cmd.stdout
63
+ # @return [Test::Command]
64
+ def stdin(data)
65
+ tap { @stdin = data }
66
+ end
67
+
44
68
  ##
45
69
  # Spawns a command
46
70
  # @return [Test::Command]
47
71
  def spawn
48
72
  return self if @spawned
73
+ @in_r = input_pipe
49
74
  tap do
50
75
  @spawned = true
51
76
  @out, @err = Pipe.pair, Pipe.pair
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
77
  @pid = Process.spawn(
78
+ @env,
60
79
  @cmd, *@argv,
61
- {out: @out.w, err: @err.w, in: IO::NULL}
80
+ {in: @in_r, out: @out.w, err: @err.w}
62
81
  )
82
+ @in_r.close unless @in_r.equal?(IO::NULL)
63
83
  @out.w.close
64
84
  @err.w.close
65
85
  @producer = Thread.new do
@@ -76,8 +96,9 @@ class Test::Command
76
96
  @stderr = ex.message
77
97
  @enoent = true
78
98
  ##
79
- # Capture a real non-zero status so predicates like
80
- # #success? work even though the command never spawned.
99
+ # Close the read end of any input pipe so a pending
100
+ # writer thread does not block forever.
101
+ @in_r.close unless @in_r.equal?(IO::NULL)
81
102
  @status = Process.waitpid2(Process.spawn("false")).last
82
103
  end
83
104
 
@@ -209,6 +230,23 @@ class Test::Command
209
230
 
210
231
  private
211
232
 
233
+ ##
234
+ # Returns a pipe pair used for the command's
235
+ # standard input. When no input is set, the child
236
+ # reads from IO::NULL. When the input is another
237
+ # command, the source command is spawned first and
238
+ # its stdout feeds the pipe.
239
+ # @return [IO]
240
+ # A read end (IO::NULL or a pipe read end)
241
+ def input_pipe
242
+ return IO::NULL if @stdin.nil?
243
+ data = self.class === @stdin ? @stdin.stdout : @stdin.to_s
244
+ r, w = IO.pipe
245
+ w.write(data)
246
+ w.close
247
+ r
248
+ end
249
+
212
250
  ##
213
251
  # Blocks until the spawned command has finished, its
214
252
  # output has been read, and its exit status captured.
data/test/cmd_test.rb CHANGED
@@ -21,6 +21,35 @@ class Test::Command
21
21
  end
22
22
  end
23
23
 
24
+ ##
25
+ # Test::Command#env
26
+ class EnvTest < Test
27
+ def test_ruby_env
28
+ assert_equal "42\n",
29
+ ::Test::Command.new("ruby", "-e", "puts ENV['FOO']")
30
+ .env("FOO" => "42")
31
+ .stdout
32
+ end
33
+ end
34
+
35
+ ##
36
+ # Test::Command#stdin
37
+ class StdinTest < Test
38
+ def test_ruby_stdin_string
39
+ assert_equal "hello world",
40
+ ::Test::Command.new("cat")
41
+ .stdin("hello world")
42
+ .stdout
43
+ end
44
+
45
+ def test_ruby_stdin_command
46
+ assert_equal "42\n",
47
+ ::Test::Command.new("cat")
48
+ .stdin(ruby("puts 42"))
49
+ .stdout
50
+ end
51
+ end
52
+
24
53
  ##
25
54
  # Test::Command#{exit_status, status, success?}
26
55
  class ExitStatusTest < Test
data/test-cmd.rb.gemspec CHANGED
@@ -5,7 +5,7 @@ Gem::Specification.new do |gem|
5
5
  gem.authors = ["robert"]
6
6
  gem.email = ["robert@r.uby.dev"]
7
7
  gem.homepage = "https://github.com/0x1eef/test-cmd.rb#readme"
8
- gem.version = "2.0.0"
8
+ gem.version = "2.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: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - robert