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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8938c9977d79ffd99b2526dc698d66f33c0afc225121307a7cbf79fe9284acdb
4
- data.tar.gz: 0fcd34ab808b76a121a581443b0bf9c075e8bf92633a112c406b11b11691b9ab
3
+ metadata.gz: bec73dcb32037bd849d44167aea25574b4575a704fc5fb88e822001667c2080a
4
+ data.tar.gz: 3ebc7e7a8be82a6e69c59fcbc29881b365e6af37c9652dc3065a8fa320bfe7b8
5
5
  SHA512:
6
- metadata.gz: e4da5cce84127eb2b3fda62ba9cb893b588ad1e68b173ad8ef558d8c7f4945c089c74dea20e7be62e7e685bd7efbcf91a6d0616bcc413a7fb4663731151c25da
7
- data.tar.gz: 7abf027ed86e29fbc531698feec84c318fda64d3138fa3e22bce81c1f2ef7a79b73aa255274de5f0265453c8df59964a616b912cb41cb55b8e692cd36b0d1fae
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 possible states of this subprocess
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SProc
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
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.3.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-27 00:00:00.000000000 Z
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