sproc 0.3.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8938c9977d79ffd99b2526dc698d66f33c0afc225121307a7cbf79fe9284acdb
4
- data.tar.gz: 0fcd34ab808b76a121a581443b0bf9c075e8bf92633a112c406b11b11691b9ab
3
+ metadata.gz: d2ccda7be2976e3c6daaa74c8a37c858fdc76f5d929e1cf87b77cc7411d9f72d
4
+ data.tar.gz: ae73fcb466456779be5e91afc1bbe8e67b368bed7f3c834a0d523acaacfdd9a9
5
5
  SHA512:
6
- metadata.gz: e4da5cce84127eb2b3fda62ba9cb893b588ad1e68b173ad8ef558d8c7f4945c089c74dea20e7be62e7e685bd7efbcf91a6d0616bcc413a7fb4663731151c25da
7
- data.tar.gz: 7abf027ed86e29fbc531698feec84c318fda64d3138fa3e22bce81c1f2ef7a79b73aa255274de5f0265453c8df59964a616b912cb41cb55b8e692cd36b0d1fae
6
+ metadata.gz: abc0511633bf642dfaffd0a35f00f9d257bc38f316010418932b910bfeebbcb7d7c7f479aa70fed8aa31fe6be24eacc19f69a0108a17d766910c7145da3e194a
7
+ data.tar.gz: 6d00c01bf0791f2919b37449cf76ee95a71dba9aa95713e0c0955dd37bcb4c9a41b7881e7a4e7058d8800c64963ea78bb804550e6bd66cf0e4e1fada40daf82f
data/lib/sproc.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "sproc/version"
4
4
  require_relative "sproc/core"
5
+ require_relative "sproc/osinfo"
5
6
 
6
7
  module SProc
7
8
  class Error < StandardError; end
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,55 @@
1
+ require 'rbconfig'
2
+
3
+ # helper methods to find out os and execution environment
4
+ module SProc
5
+ module OSInfo
6
+ # the supported exec environments
7
+ module OS
8
+ WINDOWS = 0
9
+ LINUX = 1
10
+ MINGW = 2
11
+ CYGWIN = 3
12
+ OSX = 4
13
+ BSD = 5
14
+ UNKNOWN = 100
15
+ end
16
+
17
+ # returns the current execution environment
18
+ def self.os_context
19
+ case RbConfig::CONFIG['host_os']
20
+ when /mswin/ then OS::WINDOWS
21
+ when /mingw/ then OS::MINGW
22
+ when /cygwin/ then OS::CYGWIN
23
+ when /darwin/ then OS::OSX
24
+ when /linux/ then OS::LINUX
25
+ when /bsd/ then OS::BSD
26
+ else OS::UNKNOWN
27
+ end
28
+ end
29
+
30
+ # return the current underlying operating system
31
+ def self.host_os
32
+ if [OS::WINDOWS, OS::MINGW, OS::CYGWIN].include?(os_context)
33
+ OS::WINDOWS
34
+ else
35
+ os_context
36
+ end
37
+ end
38
+
39
+ def on_windows?
40
+ OSInfo.host_os == OS::WINDOWS
41
+ end
42
+
43
+ def on_linux?
44
+ OSInfo.os_context == OS::LINUX
45
+ end
46
+
47
+ def on_bsd?
48
+ OSInfo.os_context == OS::BSD
49
+ end
50
+
51
+ def on_osx?
52
+ OSInfo.os_context == OS::OSX
53
+ end
54
+ end
55
+ end
@@ -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.task_info[:cmd_str][0,10] + "..."
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.6.1"
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.6.1
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,7 +43,8 @@ files:
43
43
  - bin/setup
44
44
  - lib/sproc.rb
45
45
  - lib/sproc/core.rb
46
- - lib/sproc/utils.rb
46
+ - lib/sproc/osinfo.rb
47
+ - lib/sproc/reporting.rb
47
48
  - lib/sproc/version.rb
48
49
  - sproc.gemspec
49
50
  homepage: https://github.com/rillbert/sproc
data/lib/sproc/utils.rb DELETED
@@ -1,53 +0,0 @@
1
- require 'rbconfig'
2
-
3
- # utility to find out info about our execution environment
4
- module OSInfo
5
- # the supported exec environments
6
- module OS
7
- WINDOWS = 0
8
- LINUX = 1
9
- MINGW = 2
10
- CYGWIN = 3
11
- OSX = 4
12
- BSD = 5
13
- UNKNOWN = 100
14
- end
15
-
16
- # returns the current execution environment
17
- def self.os_context
18
- case RbConfig::CONFIG['host_os']
19
- when /mswin/ then OS::WINDOWS
20
- when /mingw/ then OS::MINGW
21
- when /cygwin/ then OS::CYGWIN
22
- when /darwin/ then OS::OSX
23
- when /linux/ then OS::LINUX
24
- when /bsd/ then OS::BSD
25
- else OS::UNKNOWN
26
- end
27
- end
28
-
29
- # return the current underlying operating system
30
- def self.host_os
31
- if [OS::WINDOWS, OS::MINGW, OS::CYGWIN].include?(os_context)
32
- OS::WINDOWS
33
- else
34
- os_context
35
- end
36
- end
37
-
38
- def on_windows?
39
- host_os == OS::WINDOWS
40
- end
41
-
42
- def on_linux?
43
- os_context == OS::LINUX
44
- end
45
-
46
- def on_bsd?
47
- os_context == OS::BSD
48
- end
49
-
50
- def on_osx?
51
- os_context == OS::OSX
52
- end
53
- end