sproc 0.3.0 → 0.4.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 +4 -4
- data/lib/sproc/core.rb +46 -43
- data/lib/sproc/reporter.rb +196 -0
- data/lib/sproc/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bec73dcb32037bd849d44167aea25574b4575a704fc5fb88e822001667c2080a
|
4
|
+
data.tar.gz: 3ebc7e7a8be82a6e69c59fcbc29881b365e6af37c9652dc3065a8fa320bfe7b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fb7cfb22432f1a81913dfb07faf969306672f3e436b4e19451928934e169c256dd0b474f5d6ae653f49022b8a21f683bf2e31ebc808c22c7ce8463552c2298b
|
7
|
+
data.tar.gz: 03bb0859af3885655a8c26cdfce0360701b9f8aee111bf6228424f0cb63ed869814677292a845eda6f9059a2383732744ba70b33f1604c582aec67cb02605d94
|
data/lib/sproc/core.rb
CHANGED
@@ -15,7 +15,8 @@ module SProc
|
|
15
15
|
].freeze
|
16
16
|
end
|
17
17
|
|
18
|
-
# The
|
18
|
+
# The available execution states of a the subprocess
|
19
|
+
# running within an SProc instance.
|
19
20
|
module ExecutionState
|
20
21
|
NOT_STARTED = 0
|
21
22
|
RUNNING = 1
|
@@ -73,42 +74,6 @@ module SProc
|
|
73
74
|
@env = env
|
74
75
|
end
|
75
76
|
|
76
|
-
# Return the execution state of this SubProcess. Note that it is not
|
77
|
-
# identical with the life-cycle of the underlying ProcessStatus object
|
78
|
-
#
|
79
|
-
# @return current ExecutionState
|
80
|
-
def execution_state
|
81
|
-
return ExecutionState::NOT_STARTED if @execution_thread.nil?
|
82
|
-
|
83
|
-
# Count this SubProcess as running as long as the thread
|
84
|
-
# that executes it is alive (this includes book keeping
|
85
|
-
# chores within this class as well)
|
86
|
-
return ExecutionState::RUNNING if @execution_thread.alive?
|
87
|
-
|
88
|
-
status = task_info[:process_status]
|
89
|
-
|
90
|
-
# an execution thread that has run but not generated a task_info
|
91
|
-
# means that we tried to start a process but failed
|
92
|
-
return ExecutionState::FAILED_TO_START if status.nil?
|
93
|
-
|
94
|
-
# a process can terminate for different reasons:
|
95
|
-
# - its done
|
96
|
-
# - an uncaught exception-
|
97
|
-
# - an uncaught signal
|
98
|
-
|
99
|
-
# this should take care of uncaught signals
|
100
|
-
return ExecutionState::ABORTED if status.signaled?
|
101
|
-
|
102
|
-
# If the process completed (either successfully or not)
|
103
|
-
return ExecutionState::COMPLETED if status.exited?
|
104
|
-
|
105
|
-
# We don't currently handle a process that has been stopped...
|
106
|
-
raise NotImplementedError("Unhandled process 'stopped' status!") if status.stopped?
|
107
|
-
|
108
|
-
# We should never come here
|
109
|
-
raise RuntimeError("Unhandled process status: #{status.inspect}")
|
110
|
-
end
|
111
|
-
|
112
77
|
# Start the sub-process and block until it has completed.
|
113
78
|
#
|
114
79
|
#
|
@@ -147,6 +112,48 @@ module SProc
|
|
147
112
|
task_info
|
148
113
|
end
|
149
114
|
|
115
|
+
# Return the execution state of this SubProcess. Note that it is not
|
116
|
+
# identical with the life-cycle of the underlying ProcessStatus object
|
117
|
+
#
|
118
|
+
# @return current ExecutionState
|
119
|
+
def execution_state
|
120
|
+
return ExecutionState::NOT_STARTED if @execution_thread.nil?
|
121
|
+
|
122
|
+
# Count this SubProcess as running as long as the thread
|
123
|
+
# that executes it is alive (this includes book keeping
|
124
|
+
# chores within this class as well)
|
125
|
+
return ExecutionState::RUNNING if @execution_thread.alive?
|
126
|
+
|
127
|
+
status = task_info[:process_status]
|
128
|
+
|
129
|
+
# an execution thread that has run but not generated a task_info
|
130
|
+
# means that we tried to start a process but failed
|
131
|
+
return ExecutionState::FAILED_TO_START if status.nil?
|
132
|
+
|
133
|
+
# a process can terminate for different reasons:
|
134
|
+
# - its done
|
135
|
+
# - an uncaught exception-
|
136
|
+
# - an uncaught signal
|
137
|
+
|
138
|
+
# this should take care of uncaught signals
|
139
|
+
return ExecutionState::ABORTED if status.signaled?
|
140
|
+
|
141
|
+
# If the process completed (either successfully or not)
|
142
|
+
return ExecutionState::COMPLETED if status.exited?
|
143
|
+
|
144
|
+
# We don't currently handle a process that has been stopped...
|
145
|
+
raise NotImplementedError("Unhandled process 'stopped' status!") if status.stopped?
|
146
|
+
|
147
|
+
# We should never come here
|
148
|
+
raise RuntimeError("Unhandled process status: #{status.inspect}")
|
149
|
+
end
|
150
|
+
|
151
|
+
# @return the TaskInfo representing this SubProcess, nil if
|
152
|
+
# process has not started
|
153
|
+
def task_info
|
154
|
+
@runner.task_info
|
155
|
+
end
|
156
|
+
|
150
157
|
# blocks until all processes in the given array are completed/aborted.
|
151
158
|
#
|
152
159
|
# the implementation polls each process after each given poll interval
|
@@ -211,12 +218,6 @@ module SProc
|
|
211
218
|
all_proc
|
212
219
|
end
|
213
220
|
|
214
|
-
# @return the TaskInfo representing this SubProcess, nil if
|
215
|
-
# process has not started
|
216
|
-
def task_info
|
217
|
-
@runner.task_info
|
218
|
-
end
|
219
|
-
|
220
221
|
# return processes that are no longer running
|
221
222
|
def self.get_finished(running_proc)
|
222
223
|
running_proc.select do |p|
|
@@ -228,6 +229,8 @@ module SProc
|
|
228
229
|
|
229
230
|
private
|
230
231
|
|
232
|
+
# a helper method that supports both synch/async execution
|
233
|
+
# depending on the supplied args
|
231
234
|
def exec(synch, env, cmd, *args, **opts)
|
232
235
|
raise 'Subprocess already running!' unless @execution_thread.nil? || !@execution_thread.alive?
|
233
236
|
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
|
3
|
+
# This module is written to provide sub-process
|
4
|
+
# execution with some human readable logging of process start/stop/errors
|
5
|
+
#
|
6
|
+
# It wraps SProc instances and execution with calls to logging methods
|
7
|
+
# that tries to make the resulting log user friendly
|
8
|
+
module SProc
|
9
|
+
module Reporting
|
10
|
+
class << self
|
11
|
+
attr_accessor :logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def logger
|
15
|
+
Reporting.logger
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run a process synchronuously via the native shell and log
|
19
|
+
# output suitable for a build log
|
20
|
+
#
|
21
|
+
# @param cmd_name a String containing a descriptive
|
22
|
+
# name for what the process will do.
|
23
|
+
# @param cmd, args, opts see SProc.exec_sync
|
24
|
+
#
|
25
|
+
# @return the SProc instance containing the completed process
|
26
|
+
def report_sync(cmd_name, cmd, *args, **opts)
|
27
|
+
p = create_proc_and_log(cmd_name,
|
28
|
+
ShellType::NONE, :exec_sync,
|
29
|
+
cmd, args, opts)
|
30
|
+
report_completed(p)
|
31
|
+
p
|
32
|
+
end
|
33
|
+
|
34
|
+
# Run a process asynchronuously via the native shell and log
|
35
|
+
# output suitable for a build log
|
36
|
+
#
|
37
|
+
# @param cmd_name a String containing a descriptive
|
38
|
+
# name for what the process will do.
|
39
|
+
# @param cmd, args, opts see SProc.exec_sync
|
40
|
+
#
|
41
|
+
# @return the created SProc instance
|
42
|
+
def report_async(cmd_name, cmd, *args, **opts)
|
43
|
+
create_proc_and_log(cmd_name,
|
44
|
+
ShellType::NONE, :exec_async,
|
45
|
+
cmd, args, opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Run a process asynchronuously via the Bash shell and log
|
49
|
+
# output suitable for a build log
|
50
|
+
#
|
51
|
+
# @param cmd_name a String containing a descriptive
|
52
|
+
# name for what the process will do.
|
53
|
+
# @param cmd, args, opts see SProc.exec_sync
|
54
|
+
#
|
55
|
+
# @return the created SProc instance
|
56
|
+
def report_async_within_bash(cmd_name, cmd, *args, **opts)
|
57
|
+
create_proc_and_log(cmd_name,
|
58
|
+
ShellType::BASH, :exec_async,
|
59
|
+
cmd, args, opts)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Log output from a completed/aborted process
|
63
|
+
#
|
64
|
+
# @param process the SProc instance that has run
|
65
|
+
# @return true/false corresponding to process success
|
66
|
+
def report_completed(process)
|
67
|
+
friendly_name = if @log_friendly_name_map&.key?(process)
|
68
|
+
"#{@log_friendly_name_map[process]}"
|
69
|
+
else
|
70
|
+
'Process'
|
71
|
+
end
|
72
|
+
started_ok = true
|
73
|
+
case process.execution_state
|
74
|
+
when ExecutionState::COMPLETED
|
75
|
+
process.exit_zero? && log_completed_ok(friendly_name, process.task_info)
|
76
|
+
!process.exit_zero? && log_completed_err(friendly_name, process.task_info)
|
77
|
+
when ExecutionState::ABORTED
|
78
|
+
log_aborted(friendly_name, process.task_info)
|
79
|
+
started_ok = false
|
80
|
+
when ExecutionState::FAILED_TO_START
|
81
|
+
log_failed_start(friendly_name, process.task_info)
|
82
|
+
started_ok = false
|
83
|
+
else
|
84
|
+
log_unexpected(friendly_name, process.task_info)
|
85
|
+
end
|
86
|
+
started_ok && process.exit_zero?
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def create_proc_and_log(cmd_name, type, method, cmd, args, opts)
|
92
|
+
log_start(cmd_name, type, method, cmd, args, **opts)
|
93
|
+
p = SProc.new(type: type).send(method, cmd, args, **opts)
|
94
|
+
@log_friendly_name_map ||= {}
|
95
|
+
@log_friendly_name_map[p] = cmd_name
|
96
|
+
p
|
97
|
+
end
|
98
|
+
|
99
|
+
def log_method_result_ok(friendly_name, delta)
|
100
|
+
logger.info do
|
101
|
+
"#{friendly_name} completed successfully after #{delta.round(3)}s"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def log_method_result_error(friendly_name_method, delta, exc)
|
106
|
+
logger.error do
|
107
|
+
"#{friendly_name_method} aborted by #{exc.class} after #{delta.round(3)}s\n"\
|
108
|
+
"Exception info: #{exc.message}"
|
109
|
+
end
|
110
|
+
|
111
|
+
logger.debug do
|
112
|
+
exc.backtrace.to_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def log_start(cmd_name, type, method, cmd, *args, **opts)
|
117
|
+
logger.info do
|
118
|
+
async_str = method == :exec_async ? 'asynchronuously' : 'synchronuously'
|
119
|
+
type_str = type == ShellType::NONE ? 'without shell' : 'within the bash shell'
|
120
|
+
"'#{cmd_name}' executing #{async_str} #{type_str}..."
|
121
|
+
end
|
122
|
+
logger.debug do
|
123
|
+
msg = String.new("Starting #{cmd}")
|
124
|
+
msg << " with args: #{args.flatten.inspect}" unless args.nil? || args.empty?
|
125
|
+
msg << " and opts: #{opts.inspect}" unless opts.nil? || opts.empty?
|
126
|
+
msg
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def log_one_dll(regex, cmd_str, time)
|
131
|
+
m = regex.match(cmd_str)
|
132
|
+
s = m.nil? ? cmd_str : m[1]
|
133
|
+
max = 45
|
134
|
+
s = s.length > max ? s.slice(0..max - 1) : s.ljust(max)
|
135
|
+
logger.info { "#{s} took #{time.round(3)}s." }
|
136
|
+
end
|
137
|
+
|
138
|
+
def log_aborted(friendly_name, p_info)
|
139
|
+
logger.error do
|
140
|
+
"'#{friendly_name}' aborted!\n"\
|
141
|
+
"When running: #{p_info[:cmd_str]}\n"\
|
142
|
+
"#{merge_proc_output(p_info)}"\
|
143
|
+
"#{p_info[:process_status] unless p_info[:process_status].nil?}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def log_failed_start(friendly_name, p_info)
|
148
|
+
logger.error do
|
149
|
+
"'#{friendly_name}' not run!\n"\
|
150
|
+
"Could not start process using: #{p_info[:cmd_str]}\n"\
|
151
|
+
"#{merge_proc_output(p_info)}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def log_completed_ok(friendly_name, p_info)
|
156
|
+
logger.info do
|
157
|
+
"'#{friendly_name}' completed successfully after #{p_info[:wall_time].round(3)}s"
|
158
|
+
end
|
159
|
+
logger.debug do
|
160
|
+
"Cmd: #{p_info[:cmd_str]}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def log_completed_err(friendly_name, p_info)
|
165
|
+
logger.error do
|
166
|
+
"'#{friendly_name}' completed with exit code "\
|
167
|
+
"#{p_info[:process_status].exitstatus}\n"\
|
168
|
+
"When running: #{p_info[:cmd_str]}\n"\
|
169
|
+
"after #{p_info[:wall_time].round(3)}s\n"\
|
170
|
+
"#{merge_proc_output(p_info)}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def log_unexpected(friendly_name, p_info)
|
175
|
+
logger.error do
|
176
|
+
"'#{friendly_name}' caused unexpected error!"\
|
177
|
+
' Trying to display info on a running process'\
|
178
|
+
"(#{p_info[:cmd_str]})"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# @return String with sections for each non-empty output stream
|
183
|
+
# and exception messages
|
184
|
+
def merge_proc_output(p_info)
|
185
|
+
inf_str = %i[stdout stderr].collect do |sym|
|
186
|
+
next('') if p_info[sym].empty?
|
187
|
+
|
188
|
+
"--- #{sym} ---\n#{p_info[sym]}"
|
189
|
+
end.join("\n")
|
190
|
+
|
191
|
+
exc = p_info[:exception]
|
192
|
+
inf_str << "--- exception ---\n#{exc}\n" unless exc.nil?
|
193
|
+
inf_str
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
data/lib/sproc/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sproc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anders Rillbert
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- bin/setup
|
44
44
|
- lib/sproc.rb
|
45
45
|
- lib/sproc/core.rb
|
46
|
+
- lib/sproc/reporter.rb
|
46
47
|
- lib/sproc/utils.rb
|
47
48
|
- lib/sproc/version.rb
|
48
49
|
- sproc.gemspec
|