pups 1.0.0 → 1.1.1
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/.github/workflows/ci.yml +29 -0
- data/.github/workflows/lint.yml +27 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/Guardfile +3 -1
- data/README.md +108 -29
- data/Rakefile +9 -3
- data/bin/pups +4 -4
- data/lib/pups.rb +25 -11
- data/lib/pups/cli.rb +61 -27
- data/lib/pups/command.rb +14 -11
- data/lib/pups/config.rb +139 -83
- data/lib/pups/docker.rb +69 -0
- data/lib/pups/exec_command.rb +93 -81
- data/lib/pups/file_command.rb +28 -28
- data/lib/pups/merge_command.rb +48 -46
- data/lib/pups/replace_command.rb +36 -34
- data/lib/pups/runit.rb +23 -24
- data/lib/pups/version.rb +3 -1
- data/pups.gemspec +21 -16
- data/test/cli_test.rb +117 -0
- data/test/config_test.rb +192 -30
- data/test/docker_test.rb +157 -0
- data/test/exec_command_test.rb +30 -34
- data/test/file_command_test.rb +8 -9
- data/test/merge_command_test.rb +32 -32
- data/test/replace_command_test.rb +42 -44
- data/test/test_helper.rb +4 -2
- metadata +80 -16
data/test/config_test.rb
CHANGED
@@ -1,55 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class ConfigTest < MiniTest::Test
|
6
|
-
|
7
8
|
def test_config_from_env
|
8
|
-
ENV[
|
9
|
+
ENV['HELLO'] = 'world'
|
9
10
|
config = Config.new({})
|
10
|
-
assert_equal(
|
11
|
+
assert_equal('world', config.params['$ENV_HELLO'])
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def test_env_param
|
15
|
+
ENV['FOO'] = 'BAR'
|
16
|
+
config = <<~YAML
|
17
|
+
env:
|
18
|
+
BAR: baz
|
19
|
+
hello: WORLD
|
20
|
+
one: 1
|
21
|
+
YAML
|
14
22
|
|
15
|
-
|
16
|
-
|
23
|
+
config = Config.new(YAML.safe_load(config))
|
24
|
+
%w[BAR hello one].each { |e| ENV.delete(e) }
|
25
|
+
assert_equal('BAR', config.params['$ENV_FOO'])
|
26
|
+
assert_equal('baz', config.params['$ENV_BAR'])
|
27
|
+
assert_equal('WORLD', config.params['$ENV_hello'])
|
28
|
+
assert_equal('1', config.params['$ENV_one'])
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_env_with_template
|
32
|
+
ENV['FOO'] = 'BAR'
|
33
|
+
config = <<~YAML
|
34
|
+
env:
|
35
|
+
greeting: "{{hello}}, {{planet}}!"
|
36
|
+
one: 1
|
37
|
+
other: "where are we on {{planet}}?"
|
38
|
+
env_template:
|
39
|
+
planet: pluto
|
40
|
+
hello: hola
|
41
|
+
YAML
|
42
|
+
config_hash = YAML.safe_load(config)
|
43
|
+
|
44
|
+
config = Config.new(config_hash)
|
45
|
+
%w[greeting one other].each { |e| ENV.delete(e) }
|
46
|
+
assert_equal('hola, pluto!', config.params['$ENV_greeting'])
|
47
|
+
assert_equal('1', config.params['$ENV_one'])
|
48
|
+
assert_equal('BAR', config.params['$ENV_FOO'])
|
49
|
+
assert_equal('where are we on pluto?', config.params['$ENV_other'])
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_label_with_template
|
53
|
+
ENV["FOO"] = "BAR"
|
54
|
+
config = <<~YAML
|
55
|
+
env:
|
56
|
+
greeting: "{{hello}}, {{planet}}!"
|
57
|
+
one: 1
|
58
|
+
other: "where are we on {{planet}}?"
|
59
|
+
env_template:
|
60
|
+
planet: pluto
|
61
|
+
hello: hola
|
62
|
+
config: various
|
63
|
+
labels:
|
64
|
+
app_name: "{{config}}_discourse"
|
65
|
+
YAML
|
66
|
+
config_hash = YAML.load(config)
|
67
|
+
|
68
|
+
config = Config.new(config_hash)
|
69
|
+
%w[greeting one other].each { |e| ENV.delete(e) }
|
70
|
+
assert_equal("various_discourse", config.config['labels']['app_name'])
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_env_with_ENV_templated_variable
|
74
|
+
ENV['env_template_config'] = 'my_application'
|
75
|
+
config = <<~YAML
|
76
|
+
env:
|
77
|
+
greeting: "{{hello}}, {{planet}}!"
|
78
|
+
one: 1
|
79
|
+
other: "building {{config}}"
|
80
|
+
env_template:
|
81
|
+
planet: pluto
|
82
|
+
hello: hola
|
83
|
+
YAML
|
84
|
+
config_hash = YAML.safe_load(config)
|
85
|
+
|
86
|
+
config = Config.new(config_hash)
|
87
|
+
%w[greeting one other].each { |e| ENV.delete(e) }
|
88
|
+
assert_equal('hola, pluto!', config.params['$ENV_greeting'])
|
89
|
+
assert_equal('1', config.params['$ENV_one'])
|
90
|
+
assert_equal('building my_application', config.params['$ENV_other'])
|
91
|
+
ENV["env_template_config"] = nil
|
92
|
+
end
|
17
93
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
run:
|
22
|
-
- exec: echo hello world >> #{f.path}
|
23
|
-
YAML
|
94
|
+
def test_integration
|
95
|
+
f = Tempfile.new('test')
|
96
|
+
f.close
|
24
97
|
|
25
|
-
|
26
|
-
|
98
|
+
config = <<~YAML
|
99
|
+
env:
|
100
|
+
PLANET: world
|
101
|
+
params:
|
102
|
+
run: #{f.path}
|
103
|
+
greeting: hello
|
104
|
+
run:
|
105
|
+
- exec: echo $greeting $PLANET >> #{f.path}
|
106
|
+
YAML
|
27
107
|
|
108
|
+
Config.new(YAML.safe_load(config)).run
|
109
|
+
ENV.delete('PLANET')
|
110
|
+
assert_equal('hello world', File.read(f.path).strip)
|
28
111
|
ensure
|
29
112
|
f.unlink
|
30
113
|
end
|
31
114
|
|
32
115
|
def test_hooks
|
33
|
-
yaml =
|
34
|
-
run:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
hooks:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
YAML
|
116
|
+
yaml = <<~YAML
|
117
|
+
run:
|
118
|
+
- exec: 1
|
119
|
+
- exec:
|
120
|
+
hook: middle
|
121
|
+
cmd: 2
|
122
|
+
- exec: 3
|
123
|
+
hooks:
|
124
|
+
after_middle:
|
125
|
+
- exec: 2.1
|
126
|
+
before_middle:
|
127
|
+
- exec: 1.9
|
128
|
+
YAML
|
46
129
|
|
47
130
|
config = Config.load_config(yaml).config
|
48
|
-
assert_equal({
|
49
|
-
assert_equal({
|
131
|
+
assert_equal({ 'exec' => 1.9 }, config['run'][1])
|
132
|
+
assert_equal({ 'exec' => 2.1 }, config['run'][3])
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_ignored_elements
|
136
|
+
f = Tempfile.new('test')
|
137
|
+
f.close
|
138
|
+
|
139
|
+
yaml = <<~YAML
|
140
|
+
env:
|
141
|
+
PLANET: world
|
142
|
+
params:
|
143
|
+
greeting: hello
|
144
|
+
run:
|
145
|
+
- exec: 1
|
146
|
+
- exec:
|
147
|
+
hook: middle
|
148
|
+
cmd: 2
|
149
|
+
- exec: 3
|
150
|
+
- exec: echo $greeting $PLANET >> #{f.path}
|
151
|
+
hooks:
|
152
|
+
after_middle:
|
153
|
+
- exec: 2.1
|
154
|
+
before_middle:
|
155
|
+
- exec: 1.9
|
156
|
+
YAML
|
50
157
|
|
158
|
+
conf = Config.load_config(yaml, %w[hooks params])
|
159
|
+
config = conf.config
|
160
|
+
assert_equal({ 'exec' => 1 }, config['run'][0])
|
161
|
+
assert_equal({ 'exec' => { 'hook' => 'middle', 'cmd' => 2 } }, config['run'][1])
|
162
|
+
assert_equal({ 'exec' => 3 }, config['run'][2])
|
163
|
+
assert_equal({ 'exec' => "echo $greeting $PLANET >> #{f.path}" }, config['run'][3])
|
51
164
|
|
165
|
+
# $greet from params will be an empty var as it was ignored
|
166
|
+
conf.run
|
167
|
+
ENV.delete('PLANET')
|
168
|
+
assert_equal('world', File.read(f.path).strip)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_generate_docker_run_arguments
|
172
|
+
yaml = <<~YAML
|
173
|
+
env:
|
174
|
+
foo: 1
|
175
|
+
bar: 2
|
176
|
+
baz: 'hello_{{spam}}'
|
177
|
+
env_template:
|
178
|
+
spam: 'eggs'
|
179
|
+
config: my_app
|
180
|
+
expose:
|
181
|
+
- "2222:22"
|
182
|
+
- "127.0.0.1:20080:80"
|
183
|
+
- 5555
|
184
|
+
volumes:
|
185
|
+
- volume:
|
186
|
+
host: /var/discourse/shared
|
187
|
+
guest: /shared
|
188
|
+
- volume:
|
189
|
+
host: /bar
|
190
|
+
guest: /baz
|
191
|
+
links:
|
192
|
+
- link:
|
193
|
+
name: postgres
|
194
|
+
alias: postgres
|
195
|
+
- link:
|
196
|
+
name: foo
|
197
|
+
alias: bar
|
198
|
+
labels:
|
199
|
+
monitor: "true"
|
200
|
+
app_name: "{{config}}_discourse"
|
201
|
+
YAML
|
202
|
+
|
203
|
+
config = Config.load_config(yaml)
|
204
|
+
args = config.generate_docker_run_arguments
|
205
|
+
|
206
|
+
expected = []
|
207
|
+
expected << "--env foo=1 --env bar=2 --env baz=hello_eggs"
|
208
|
+
expected << "--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555"
|
209
|
+
expected << "--volume /var/discourse/shared:/shared --volume /bar:/baz"
|
210
|
+
expected << "--link postgres:postgres --link foo:bar"
|
211
|
+
expected << "--label monitor=true --label app_name=my_app_discourse"
|
212
|
+
expected.sort!
|
213
|
+
|
214
|
+
assert_equal(expected.join(" "), args)
|
52
215
|
end
|
53
216
|
end
|
54
217
|
end
|
55
|
-
|
data/test/docker_test.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'test_helper'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
module Pups
|
7
|
+
class DockerTest < MiniTest::Test
|
8
|
+
def test_gen_env_arguments
|
9
|
+
yaml = <<~YAML
|
10
|
+
env:
|
11
|
+
foo: 1
|
12
|
+
bar: 2
|
13
|
+
baz: 'hello_{{spam}}'
|
14
|
+
env_template:
|
15
|
+
spam: 'eggs'
|
16
|
+
YAML
|
17
|
+
|
18
|
+
config = Config.load_config(yaml)
|
19
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
20
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
21
|
+
assert_equal("--env foo=1 --env bar=2 --env baz=hello_eggs", args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_gen_env_arguments_empty
|
25
|
+
yaml = <<~YAML
|
26
|
+
env:
|
27
|
+
foo: 1
|
28
|
+
bar: 2
|
29
|
+
baz: ''
|
30
|
+
YAML
|
31
|
+
|
32
|
+
config = Config.load_config(yaml)
|
33
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
34
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
35
|
+
assert_equal("--env foo=1 --env bar=2", args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_gen_env_arguments_escaped
|
39
|
+
yaml = <<~YAML
|
40
|
+
env:
|
41
|
+
password: "{{spam}}*`echo`@e$t| = >>$()&list;#"
|
42
|
+
env_template:
|
43
|
+
spam: 'eggs'
|
44
|
+
YAML
|
45
|
+
|
46
|
+
config = Config.load_config(yaml)
|
47
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
48
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
49
|
+
assert_equal("--env password=#{Shellwords.escape('eggs*`echo`@e$t| = >>$()&list;#')}", args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_gen_env_arguments_quoted_with_a_space
|
53
|
+
yaml = <<~YAML
|
54
|
+
env:
|
55
|
+
a_variable: here is a sentence
|
56
|
+
YAML
|
57
|
+
|
58
|
+
config = Config.load_config(yaml)
|
59
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
60
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
61
|
+
assert_equal('--env a_variable=here\ is\ a\ sentence', args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_gen_env_arguments_newline
|
65
|
+
pw = <<~PW
|
66
|
+
this password is
|
67
|
+
a weird one
|
68
|
+
PW
|
69
|
+
|
70
|
+
yaml = <<~YAML
|
71
|
+
env:
|
72
|
+
password: "#{pw}"
|
73
|
+
env_template:
|
74
|
+
spam: 'eggs'
|
75
|
+
YAML
|
76
|
+
|
77
|
+
config = Config.load_config(yaml)
|
78
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["env"])
|
79
|
+
args = Docker.generate_env_arguments(config.config["env"])
|
80
|
+
assert_equal('--env password=this\ password\ is\ a\ weird\ one\ ', args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_gen_expose_arguments
|
84
|
+
yaml = <<~YAML
|
85
|
+
expose:
|
86
|
+
- "2222:22"
|
87
|
+
- "127.0.0.1:20080:80"
|
88
|
+
- 5555
|
89
|
+
YAML
|
90
|
+
|
91
|
+
config = Config.load_config(yaml)
|
92
|
+
args = Docker.generate_expose_arguments(config.config["expose"])
|
93
|
+
assert_equal("--publish 2222:22 --publish 127.0.0.1:20080:80 --expose 5555", args)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_gen_volume_arguments
|
97
|
+
yaml = <<~YAML
|
98
|
+
volumes:
|
99
|
+
- volume:
|
100
|
+
host: /var/discourse/shared
|
101
|
+
guest: /shared
|
102
|
+
- volume:
|
103
|
+
host: /bar
|
104
|
+
guest: /baz
|
105
|
+
YAML
|
106
|
+
|
107
|
+
config = Config.load_config(yaml)
|
108
|
+
args = Docker.generate_volume_arguments(config.config["volumes"])
|
109
|
+
assert_equal("--volume /var/discourse/shared:/shared --volume /bar:/baz", args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_gen_link_arguments
|
113
|
+
yaml = <<~YAML
|
114
|
+
links:
|
115
|
+
- link:
|
116
|
+
name: postgres
|
117
|
+
alias: postgres
|
118
|
+
- link:
|
119
|
+
name: foo
|
120
|
+
alias: bar
|
121
|
+
YAML
|
122
|
+
|
123
|
+
config = Config.load_config(yaml)
|
124
|
+
args = Docker.generate_link_arguments(config.config["links"])
|
125
|
+
assert_equal("--link postgres:postgres --link foo:bar", args)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_gen_label_arguments
|
129
|
+
yaml = <<~YAML
|
130
|
+
env_template:
|
131
|
+
config: my_app
|
132
|
+
labels:
|
133
|
+
monitor: "true"
|
134
|
+
app_name: "{{config}}_discourse"
|
135
|
+
YAML
|
136
|
+
|
137
|
+
config = Config.load_config(yaml)
|
138
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["labels"])
|
139
|
+
args = Docker.generate_label_arguments(config.config["labels"])
|
140
|
+
assert_equal("--label monitor=true --label app_name=my_app_discourse", args)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_gen_label_arguments_escaped
|
144
|
+
yaml = <<~YAML
|
145
|
+
labels:
|
146
|
+
app_name: "{{config}}'s_di$course"
|
147
|
+
env_template:
|
148
|
+
config: my_app
|
149
|
+
YAML
|
150
|
+
|
151
|
+
config = Config.load_config(yaml)
|
152
|
+
Config.transform_config_with_templated_vars(config.config['env_template'], config.config["labels"])
|
153
|
+
args = Docker.generate_label_arguments(config.config["labels"])
|
154
|
+
assert_equal("--label app_name=#{Shellwords.escape("my_app's_di$course")}", args)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/test/exec_command_test.rb
CHANGED
@@ -1,113 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
4
6
|
module Pups
|
5
7
|
class ExecCommandTest < MiniTest::Test
|
6
|
-
|
7
|
-
def from_str(str, params={})
|
8
|
+
def from_str(str, params = {})
|
8
9
|
ExecCommand.from_str(str, params).commands
|
9
10
|
end
|
10
11
|
|
11
|
-
def from_hash(hash, params={})
|
12
|
+
def from_hash(hash, params = {})
|
12
13
|
ExecCommand.from_hash(hash, params).commands
|
13
14
|
end
|
14
15
|
|
15
16
|
def test_simple_str_command
|
16
|
-
assert_equal([
|
17
|
-
|
17
|
+
assert_equal(['do_something'],
|
18
|
+
from_str('do_something'))
|
18
19
|
end
|
19
20
|
|
20
21
|
def test_simple_str_command_with_param
|
21
|
-
assert_equal([
|
22
|
-
|
22
|
+
assert_equal(['hello world'],
|
23
|
+
from_str('hello $bob', { 'bob' => 'world' }))
|
23
24
|
end
|
24
25
|
|
25
26
|
def test_nested_command
|
26
|
-
assert_equal([
|
27
|
-
|
27
|
+
assert_equal(['first'],
|
28
|
+
from_hash('cmd' => 'first'))
|
28
29
|
end
|
29
30
|
|
30
31
|
def test_multi_commands
|
31
|
-
assert_equal([
|
32
|
-
|
32
|
+
assert_equal(%w[first second],
|
33
|
+
from_hash('cmd' => %w[first second]))
|
33
34
|
end
|
34
35
|
|
35
36
|
def test_multi_commands_with_home
|
36
|
-
assert_equal([
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
assert_equal(['cd /home/sam && first',
|
38
|
+
'cd /home/sam && second'],
|
39
|
+
from_hash('cmd' => %w[first second],
|
40
|
+
'cd' => '/home/sam'))
|
40
41
|
end
|
41
42
|
|
42
43
|
def test_exec_works
|
43
|
-
ExecCommand.from_str(
|
44
|
+
ExecCommand.from_str('ls', {}).run
|
44
45
|
end
|
45
46
|
|
46
47
|
def test_fails_for_bad_command
|
47
48
|
assert_raises(Errno::ENOENT) do
|
48
|
-
ExecCommand.from_str(
|
49
|
+
ExecCommand.from_str('boom', {}).run
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
53
|
def test_backgroud_task_do_not_fail
|
53
54
|
cmd = ExecCommand.new({})
|
54
55
|
cmd.background = true
|
55
|
-
cmd.add(
|
56
|
+
cmd.add('sleep 10 && exit 1')
|
56
57
|
cmd.run
|
57
58
|
end
|
58
59
|
|
59
60
|
def test_raise_on_fail
|
60
61
|
cmd = ExecCommand.new({})
|
61
|
-
cmd.add(
|
62
|
+
cmd.add('chgrp -a')
|
62
63
|
cmd.raise_on_fail = false
|
63
64
|
cmd.run
|
64
65
|
end
|
65
66
|
|
66
67
|
def test_stdin
|
67
|
-
|
68
68
|
`touch test_file`
|
69
69
|
cmd = ExecCommand.new({})
|
70
|
-
cmd.add(
|
71
|
-
cmd.stdin =
|
70
|
+
cmd.add('read test ; echo $test > test_file')
|
71
|
+
cmd.stdin = 'hello'
|
72
72
|
cmd.run
|
73
73
|
|
74
|
-
assert_equal("hello\n", File.read(
|
75
|
-
|
74
|
+
assert_equal("hello\n", File.read('test_file'))
|
76
75
|
ensure
|
77
|
-
File.delete(
|
76
|
+
File.delete('test_file')
|
78
77
|
end
|
79
78
|
|
80
79
|
def test_fails_for_non_zero_exit
|
81
|
-
assert_raises(
|
82
|
-
ExecCommand.from_str(
|
80
|
+
assert_raises(Pups::ExecError) do
|
81
|
+
ExecCommand.from_str('chgrp -a', {}).run
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
86
|
-
|
87
85
|
def test_can_terminate_async
|
88
86
|
cmd = ExecCommand.new({})
|
89
87
|
cmd.background = true
|
90
|
-
pid = cmd.spawn(
|
88
|
+
pid = cmd.spawn('sleep 10 && exit 1')
|
91
89
|
ExecCommand.terminate_async
|
92
90
|
assert_raises(Errno::ECHILD) do
|
93
|
-
Process.waitpid(pid,Process::WNOHANG)
|
91
|
+
Process.waitpid(pid, Process::WNOHANG)
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
97
95
|
def test_can_terminate_rogues
|
98
96
|
cmd = ExecCommand.new({})
|
99
97
|
cmd.background = true
|
100
|
-
pid = cmd.spawn(
|
98
|
+
pid = cmd.spawn('trap "echo TERM && sleep 100" TERM ; sleep 100')
|
101
99
|
# we need to give bash enough time to trap
|
102
100
|
sleep 0.01
|
103
101
|
|
104
102
|
ExecCommand.terminate_async(wait: 0.1)
|
105
103
|
|
106
104
|
assert_raises(Errno::ECHILD) do
|
107
|
-
Process.waitpid(pid,Process::WNOHANG)
|
105
|
+
Process.waitpid(pid, Process::WNOHANG)
|
108
106
|
end
|
109
|
-
|
110
107
|
end
|
111
|
-
|
112
108
|
end
|
113
109
|
end
|