tork 15.0.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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/HISTORY.markdown +962 -0
- data/LICENSE +21 -0
- data/README.markdown +305 -0
- data/Rakefile +2 -0
- data/bin/tork +105 -0
- data/bin/tork-driver +86 -0
- data/bin/tork-herald +47 -0
- data/bin/tork-master +87 -0
- data/lib/tork/client.rb +34 -0
- data/lib/tork/config.rb +83 -0
- data/lib/tork/config/parallel_tests.rb +7 -0
- data/lib/tork/config/rails.rb +43 -0
- data/lib/tork/driver.rb +136 -0
- data/lib/tork/master.rb +99 -0
- data/lib/tork/server.rb +38 -0
- data/lib/tork/version.rb +3 -0
- data/man/man1/tork-driver.1 +70 -0
- data/man/man1/tork-herald.1 +22 -0
- data/man/man1/tork-master.1 +61 -0
- data/man/man1/tork.1 +27 -0
- data/tork.gemspec +23 -0
- metadata +138 -0
data/bin/tork-herald
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
=begin
|
3
|
+
|
4
|
+
TORK-HERALD 1 2012-01-23 15.0.0
|
5
|
+
==============================================================================
|
6
|
+
|
7
|
+
NAME
|
8
|
+
------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
tork-herald - reports modified files
|
11
|
+
|
12
|
+
SYNOPSIS
|
13
|
+
------------------------------------------------------------------------------
|
14
|
+
|
15
|
+
`tork-herald` [*OPTION*]...
|
16
|
+
|
17
|
+
DESCRIPTION
|
18
|
+
------------------------------------------------------------------------------
|
19
|
+
|
20
|
+
This program monitors the current working directory and prints relative paths
|
21
|
+
of modified files, one per line, to the standard output stream.
|
22
|
+
|
23
|
+
OPTIONS
|
24
|
+
------------------------------------------------------------------------------
|
25
|
+
|
26
|
+
`-h`, `--help`
|
27
|
+
Display this help manual using man(1).
|
28
|
+
|
29
|
+
SEE ALSO
|
30
|
+
------------------------------------------------------------------------------
|
31
|
+
|
32
|
+
tork(1), tork-driver(1), tork-master(1), tork-herald(1)
|
33
|
+
|
34
|
+
=end =========================================================================
|
35
|
+
|
36
|
+
$0 = File.basename(__FILE__) # for easier indentification in ps(1) output
|
37
|
+
|
38
|
+
require 'binman'
|
39
|
+
BinMan.help
|
40
|
+
|
41
|
+
require 'guard'
|
42
|
+
require 'guard/listener'
|
43
|
+
|
44
|
+
listener = Guard::Listener.select_and_init(:watch_all_modifications => true)
|
45
|
+
listener.on_change {|files| puts files }
|
46
|
+
STDOUT.sync = true # don't buffer puts()
|
47
|
+
listener.start
|
data/bin/tork-master
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
=begin
|
3
|
+
|
4
|
+
TORK-MASTER 1 2012-01-23 15.0.0
|
5
|
+
==============================================================================
|
6
|
+
|
7
|
+
NAME
|
8
|
+
------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
tork-master - absorbs overhead and runs tests
|
11
|
+
|
12
|
+
SYNOPSIS
|
13
|
+
------------------------------------------------------------------------------
|
14
|
+
|
15
|
+
`tork-master` [*OPTION*]...
|
16
|
+
|
17
|
+
DESCRIPTION
|
18
|
+
------------------------------------------------------------------------------
|
19
|
+
|
20
|
+
This program reads the following single-line commands (JSON arrays) from its
|
21
|
+
standard input stream and performs the respective actions as described below.
|
22
|
+
|
23
|
+
`["load",` *paths*`,` *files*`]`
|
24
|
+
Adds the given array of *paths* to Ruby's $LOAD_PATH, loads the given array
|
25
|
+
of *files* after removing their ".rb" file extension if present, and prints
|
26
|
+
the given command line to the standard output stream.
|
27
|
+
|
28
|
+
`["test",` *test_file*`,` *test_names*`]`
|
29
|
+
Runs the given *test_file* in a forked child process while instructing your
|
30
|
+
chosen unit testing framework (loaded by your test execution overhead) to
|
31
|
+
only run those tests that are named in the given array of *test_names*.
|
32
|
+
|
33
|
+
Prints the given command line to the standard output stream immediately
|
34
|
+
after forking the child process.
|
35
|
+
|
36
|
+
Prints the given command line, modified with `"pass"` (if the test passed)
|
37
|
+
or `"fail"` (if the test failed) in place of `"test"`, to the standard
|
38
|
+
output stream after the forked child process finishes.
|
39
|
+
|
40
|
+
The standard output and error streams of the forked child process are
|
41
|
+
redirected to a file whose path and name are the same as that of the test
|
42
|
+
file being run by the forked child process but with ".log" appended.
|
43
|
+
|
44
|
+
`["stop"]`
|
45
|
+
Stops all tests that are currently running and prints the given command line
|
46
|
+
to the standard output stream.
|
47
|
+
|
48
|
+
`["quit"]`
|
49
|
+
Stops all tests that are currently running and exits.
|
50
|
+
|
51
|
+
OPTIONS
|
52
|
+
------------------------------------------------------------------------------
|
53
|
+
|
54
|
+
`-h`, `--help`
|
55
|
+
Display this help manual using man(1).
|
56
|
+
|
57
|
+
FILES
|
58
|
+
------------------------------------------------------------------------------
|
59
|
+
|
60
|
+
*.tork.rb*
|
61
|
+
Optional Ruby script for configuring tork(1).
|
62
|
+
|
63
|
+
ENVIRONMENT
|
64
|
+
------------------------------------------------------------------------------
|
65
|
+
|
66
|
+
`TORK_CONFIGS`
|
67
|
+
A single-line JSON array containing paths to actual files or names of
|
68
|
+
helper libraries in the tork/config/ namespace of Ruby's load path.
|
69
|
+
These configuration files are loaded just before *.tork.rb* is loaded.
|
70
|
+
|
71
|
+
SEE ALSO
|
72
|
+
------------------------------------------------------------------------------
|
73
|
+
|
74
|
+
tork(1), tork-driver(1), tork-master(1), tork-herald(1)
|
75
|
+
|
76
|
+
=end =========================================================================
|
77
|
+
|
78
|
+
$0 = File.basename(__FILE__) # for easier indentification in ps(1) output
|
79
|
+
|
80
|
+
require 'binman'
|
81
|
+
BinMan.help
|
82
|
+
|
83
|
+
require 'tork/master'
|
84
|
+
Tork::Master.loop
|
85
|
+
|
86
|
+
Process.waitall
|
87
|
+
raise SystemExit # prevent empty test suite from running in the master process
|
data/lib/tork/client.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Tork
|
2
|
+
module Client
|
3
|
+
|
4
|
+
class Receiver < Thread
|
5
|
+
def initialize *popen_args
|
6
|
+
(@io = IO.popen(*popen_args)).sync = true
|
7
|
+
super() { loop { yield @io.gets } }
|
8
|
+
end
|
9
|
+
|
10
|
+
def quit
|
11
|
+
kill # stop receive loop
|
12
|
+
Process.kill :SIGTERM, @io.pid
|
13
|
+
Process.wait @io.pid # reap zombie
|
14
|
+
@io.close # prevent further I/O
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Transceiver < Receiver
|
19
|
+
def initialize *popen_args
|
20
|
+
@io_write_lock = Mutex.new
|
21
|
+
popen_args[1] = 'w+'
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def send command
|
26
|
+
@io_write_lock.synchronize do
|
27
|
+
warn "#{caller[2]} SEND #{command.inspect}" if $DEBUG
|
28
|
+
@io.puts JSON.dump(command)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/lib/tork/config.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Tork
|
4
|
+
_user_config_file = '.tork.rb'
|
5
|
+
|
6
|
+
Config = OpenStruct.new
|
7
|
+
|
8
|
+
#---------------------------------------------------------------------------
|
9
|
+
# defaults
|
10
|
+
#---------------------------------------------------------------------------
|
11
|
+
|
12
|
+
Config.max_forked_workers = [
|
13
|
+
# http://stackoverflow.com/questions/891537#6420817
|
14
|
+
'fgrep -c processor /proc/cpuinfo', # Linux
|
15
|
+
'sysctl -n hw.ncpu', # BSD
|
16
|
+
'hwprefs cpu_count', # Darwin 9
|
17
|
+
'hwprefs thread_count', # Darwin 10
|
18
|
+
].map {|cmd| `#{cmd} 2>/dev/null`.to_i }.push(1).max
|
19
|
+
|
20
|
+
Config.overhead_load_paths = ['lib', 'test', 'spec']
|
21
|
+
|
22
|
+
Config.overhead_file_globs = ['{test,spec}/{test,spec}_helper.rb']
|
23
|
+
|
24
|
+
Config.reabsorb_file_greps = [/^#{Regexp.quote(_user_config_file)}$/,
|
25
|
+
%r<(test|spec)/\1_helper\.rb>]
|
26
|
+
|
27
|
+
Config.all_test_file_globs = ['{test,spec}/**/*_{test,spec}.rb']
|
28
|
+
|
29
|
+
Config.test_file_globbers = {
|
30
|
+
# source files that correspond to test files
|
31
|
+
%r<^lib/.+\.rb$> => lambda do |path|
|
32
|
+
base = File.basename(path, '.rb')
|
33
|
+
"{test,spec}/**/#{base}_{test,spec}.rb"
|
34
|
+
end,
|
35
|
+
|
36
|
+
# the actual test files themselves
|
37
|
+
%r<^(test|spec)/.+_\1\.rb$> => lambda {|path| path }
|
38
|
+
}
|
39
|
+
|
40
|
+
Config.test_name_extractor = lambda do |line|
|
41
|
+
case line
|
42
|
+
when /^\s*def\s+test_(\w+)/ then $1
|
43
|
+
when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/ then $3
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Config.before_fork_hooks = []
|
48
|
+
|
49
|
+
Config.after_fork_hooks = [
|
50
|
+
# tell testing framework to only run the named tests inside the test file
|
51
|
+
lambda do |worker_number, log_file, test_file, test_names|
|
52
|
+
unless test_names.empty?
|
53
|
+
case File.basename(test_file)
|
54
|
+
when /(\b|_)test(\b|_)/ # Test::Unit
|
55
|
+
ARGV.push '--name', "/(?i:#{
|
56
|
+
test_names.map do |name|
|
57
|
+
# elide string interpolation and invalid method name characters
|
58
|
+
name.gsub(/\#\{.*?\}/, ' ').strip.gsub(/\W+/, '.*')
|
59
|
+
end.join('|')
|
60
|
+
})/"
|
61
|
+
when /(\b|_)spec(\b|_)/ # RSpec
|
62
|
+
test_names.each {|name| ARGV.push '--example', name }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
]
|
67
|
+
|
68
|
+
#---------------------------------------------------------------------------
|
69
|
+
# overrides
|
70
|
+
#---------------------------------------------------------------------------
|
71
|
+
|
72
|
+
if ENV.key? 'TORK_CONFIGS'
|
73
|
+
JSON.load(ENV['TORK_CONFIGS']).each do |config|
|
74
|
+
if File.exist? config
|
75
|
+
load File.expand_path(config)
|
76
|
+
else
|
77
|
+
require "tork/config/#{config}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
load _user_config_file if File.exist? _user_config_file
|
83
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'tork/config'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
Tork::Config.reabsorb_file_greps.push(
|
5
|
+
%r<^config/.+\.(rb|yml)$>,
|
6
|
+
%r<^db/schema\.rb$>,
|
7
|
+
%r<^Gemfile\.lock$>
|
8
|
+
)
|
9
|
+
|
10
|
+
Tork::Config.test_file_globbers[%r<^(app|lib|test|spec)/.+\.rb$>] =
|
11
|
+
lambda do |path|
|
12
|
+
base = File.basename(path, '.rb')
|
13
|
+
poly = ActiveSupport::Inflector.pluralize(base)
|
14
|
+
"{test,spec}/**/{#{base},#{poly}_*}_{test,spec}.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
Tork::Config.test_file_globbers[%r<^(test|spec)/factories/.+_factory\.rb$>] =
|
18
|
+
lambda do |path|
|
19
|
+
base = File.basename(path, '_factory.rb')
|
20
|
+
poly = ActiveSupport::Inflector.pluralize(base)
|
21
|
+
"{test,spec}/**/{#{base},#{poly}_*}_{test,spec}.rb"
|
22
|
+
end
|
23
|
+
|
24
|
+
Tork::Config.after_fork_hooks << proc do
|
25
|
+
unless ActiveRecord::Base.connection_config[:database] == ':memory:'
|
26
|
+
ActiveRecord::Base.connection.reconnect!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rails/railtie'
|
32
|
+
Class.new Rails::Railtie do
|
33
|
+
config.before_initialize do |app|
|
34
|
+
if app.config.cache_classes
|
35
|
+
warn "tork/config/rails: Setting #{app.class}.config.cache_classes = false"
|
36
|
+
app.config.cache_classes = false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
warn "tork/config/rails: Railtie not available; please manually set:\n\t"\
|
42
|
+
"config.cache_classes = false"
|
43
|
+
end
|
data/lib/tork/driver.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'diff/lcs'
|
3
|
+
require 'tork/client'
|
4
|
+
require 'tork/server'
|
5
|
+
require 'tork/config'
|
6
|
+
|
7
|
+
module Tork
|
8
|
+
module Driver
|
9
|
+
|
10
|
+
extend Server
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def run_all_test_files
|
14
|
+
run_test_files Dir[*Config.all_test_file_globs]
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop_running_test_files
|
18
|
+
@master.send [:stop]
|
19
|
+
@running_test_files.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
def rerun_passed_test_files
|
23
|
+
run_test_files @passed_test_files
|
24
|
+
end
|
25
|
+
|
26
|
+
def rerun_failed_test_files
|
27
|
+
run_test_files @failed_test_files
|
28
|
+
end
|
29
|
+
|
30
|
+
def reabsorb_overhead_files
|
31
|
+
@master.quit if defined? @master
|
32
|
+
|
33
|
+
@master = Client::Transceiver.new('tork-master') do |line|
|
34
|
+
event, file, tests = JSON.load(line)
|
35
|
+
|
36
|
+
case event.to_sym
|
37
|
+
when :test
|
38
|
+
@waiting_test_files.delete file
|
39
|
+
@running_test_files.push file
|
40
|
+
|
41
|
+
when :pass
|
42
|
+
@running_test_files.delete file
|
43
|
+
@failed_test_files.delete file
|
44
|
+
if tests.empty? and not @passed_test_files.include? file
|
45
|
+
@passed_test_files.push file
|
46
|
+
end
|
47
|
+
|
48
|
+
when :fail
|
49
|
+
@running_test_files.delete file
|
50
|
+
@passed_test_files.delete file
|
51
|
+
@failed_test_files.push file unless @failed_test_files.include? file
|
52
|
+
end
|
53
|
+
|
54
|
+
@client.print line
|
55
|
+
end
|
56
|
+
|
57
|
+
@master.send [:load, Config.overhead_load_paths,
|
58
|
+
Dir[*Config.overhead_file_globs]]
|
59
|
+
|
60
|
+
rerun_running_test_files
|
61
|
+
end
|
62
|
+
|
63
|
+
def loop
|
64
|
+
reabsorb_overhead_files
|
65
|
+
|
66
|
+
@herald = Client::Receiver.new('tork-herald') do |line|
|
67
|
+
changed_file = line.chomp
|
68
|
+
warn "tork-driver: herald: #{changed_file}" if $DEBUG
|
69
|
+
|
70
|
+
# find and run the tests that correspond to the changed file
|
71
|
+
Config.test_file_globbers.each do |regexp, globber|
|
72
|
+
if regexp =~ changed_file and glob = globber.call(changed_file)
|
73
|
+
run_test_files Dir[glob]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# reabsorb text execution overhead if overhead files changed
|
78
|
+
if Config.reabsorb_file_greps.any? {|r| r =~ changed_file }
|
79
|
+
@client.puts JSON.dump([:over, changed_file])
|
80
|
+
# NOTE: new thread because reabsorb_overhead_files will kill this one
|
81
|
+
Thread.new { reabsorb_overhead_files }.join
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
super
|
86
|
+
|
87
|
+
@herald.quit
|
88
|
+
@master.quit
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
@waiting_test_files = []
|
94
|
+
@running_test_files = []
|
95
|
+
@passed_test_files = []
|
96
|
+
@failed_test_files = []
|
97
|
+
|
98
|
+
def rerun_running_test_files
|
99
|
+
run_test_files @running_test_files
|
100
|
+
end
|
101
|
+
|
102
|
+
def run_test_files files
|
103
|
+
files.each {|f| run_test_file f }
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_test_file file
|
107
|
+
if File.exist? file and not @waiting_test_files.include? file
|
108
|
+
@waiting_test_files.push file
|
109
|
+
@master.send [:test, file, find_changed_test_names(file)]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
@lines_by_file = {}
|
114
|
+
|
115
|
+
def find_changed_test_names test_file
|
116
|
+
# cache the contents of the test file for diffing below
|
117
|
+
new_lines = File.readlines(test_file)
|
118
|
+
old_lines = @lines_by_file[test_file] || new_lines
|
119
|
+
@lines_by_file[test_file] = new_lines
|
120
|
+
|
121
|
+
# find which tests have changed inside the given test file
|
122
|
+
Diff::LCS.diff(old_lines, new_lines).flatten.map do |change|
|
123
|
+
catch :found do
|
124
|
+
# search backwards from the line that changed up to
|
125
|
+
# the first line in the file for test definitions
|
126
|
+
change.position.downto(0) do |i|
|
127
|
+
if test_name = Config.test_name_extractor.call(new_lines[i])
|
128
|
+
throw :found, test_name
|
129
|
+
end
|
130
|
+
end; nil # prevent unsuccessful search from returning an integer
|
131
|
+
end
|
132
|
+
end.compact.uniq
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
data/lib/tork/master.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'tork/server'
|
3
|
+
require 'tork/config'
|
4
|
+
|
5
|
+
module Tork
|
6
|
+
module Master
|
7
|
+
|
8
|
+
extend Server
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def load paths, files
|
12
|
+
$LOAD_PATH.unshift(*paths)
|
13
|
+
|
14
|
+
files.each do |file|
|
15
|
+
branch, leaf = File.split(file)
|
16
|
+
file = leaf if paths.include? branch
|
17
|
+
require file.sub(/\.rb$/, '')
|
18
|
+
end
|
19
|
+
|
20
|
+
@client.print @command_line
|
21
|
+
end
|
22
|
+
|
23
|
+
def test test_file, test_names
|
24
|
+
# throttle forking rate to meet the maximum concurrent workers limit
|
25
|
+
sleep 1 until @command_by_worker_pid.size < Config.max_forked_workers
|
26
|
+
|
27
|
+
log_file = test_file + '.log'
|
28
|
+
worker_number = @worker_number_pool.shift
|
29
|
+
|
30
|
+
Config.before_fork_hooks.each do |hook|
|
31
|
+
hook.call worker_number, log_file, test_file, test_names
|
32
|
+
end
|
33
|
+
|
34
|
+
worker_pid = fork do
|
35
|
+
# make the process title Test::Unit friendly and ps(1) searchable
|
36
|
+
$0 = "tork-worker[#{worker_number}] #{test_file}"
|
37
|
+
|
38
|
+
# detach worker process from master process' group for kill -pgrp
|
39
|
+
Process.setsid
|
40
|
+
|
41
|
+
# detach worker process from master process' standard input stream
|
42
|
+
STDIN.reopen IO.pipe.first
|
43
|
+
|
44
|
+
# capture test output in log file because tests are run in parallel
|
45
|
+
# which makes it difficult to understand interleaved output thereof
|
46
|
+
STDERR.reopen(STDOUT.reopen(log_file, 'w')).sync = true
|
47
|
+
|
48
|
+
Config.after_fork_hooks.each do |hook|
|
49
|
+
hook.call worker_number, log_file, test_file, test_names
|
50
|
+
end
|
51
|
+
|
52
|
+
# after loading the user's test file, the at_exit() hook of the user's
|
53
|
+
# testing framework will take care of running the tests and reflecting
|
54
|
+
# any failures in the worker process' exit status, which will then be
|
55
|
+
# handled by the SIGCHLD trap registered in the master process (below)
|
56
|
+
Kernel.load test_file
|
57
|
+
end
|
58
|
+
|
59
|
+
@command_by_worker_pid[worker_pid] = @command.push(worker_number)
|
60
|
+
@client.print @command_line
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop
|
64
|
+
# NOTE: the SIGCHLD handler will reap these killed worker processes
|
65
|
+
Process.kill :SIGTERM, *@command_by_worker_pid.keys.map {|pid| -pid }
|
66
|
+
rescue ArgumentError, SystemCallError
|
67
|
+
# some workers might have already exited before we sent them the signal
|
68
|
+
end
|
69
|
+
|
70
|
+
def loop
|
71
|
+
super
|
72
|
+
stop
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
@worker_number_pool = (0 ... Config.max_forked_workers).to_a
|
78
|
+
@command_by_worker_pid = {}
|
79
|
+
|
80
|
+
# process exited child processes and report finished workers to client
|
81
|
+
trap :SIGCHLD do
|
82
|
+
begin
|
83
|
+
while wait2_array = Process.wait2(-1, Process::WNOHANG)
|
84
|
+
child_pid, child_status = wait2_array
|
85
|
+
if command = @command_by_worker_pid.delete(child_pid)
|
86
|
+
@worker_number_pool.push command.pop
|
87
|
+
command[0] = child_status.success? ? 'pass' : 'fail'
|
88
|
+
@client.puts JSON.dump(command.push(child_status))
|
89
|
+
else
|
90
|
+
warn "tork-master: unknown child exited: #{wait2_array.inspect}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
rescue SystemCallError
|
94
|
+
# raised by wait2() when there are currently no child processes
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|