multi_process 1.1.1 → 1.2.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: ea43e9440c1ed8b4e62d74a24514447389c104eddc20df1870f14ef8316f90e7
4
- data.tar.gz: 52de1eb70c9832e2ef8e0fcd53fe247905342bf45a28b40b852365d413a68136
3
+ metadata.gz: dddbd3442f480abb21f2e64ce86365471a4f04e6832e385f07e9d52a45038e95
4
+ data.tar.gz: fb7b5bcb5f053cacac6d3c2cbe2ff84ef200622fa06b8c045db6105051807ab2
5
5
  SHA512:
6
- metadata.gz: 2ed3835793ff96ab2696b4521f93961f46267c4e75d5baa44c5bc45082c888bfbd691ac7d730596377727863c3b9f6b8d33943cdbaf55f49c560979c6affe0be
7
- data.tar.gz: 95f65e1e3e3c2afb26ca37e99706271774bd949111260a02468036b86fe6e51ddf4cedfa58089154b626217d58e7e72b0affae12f60b4d17424cefffb096b716
6
+ metadata.gz: 92843a792257de7057e597f062ac53c690ccddd223dfdcfae94e42917a74cf0b299def05a691b880737cb7e51670ca6b343bf788e743201b312c8a32ce2fb0df
7
+ data.tar.gz: c3db19666a44e40c06d4f321079bddf8182e5c2169cd616cc4855fbb3a0e90818bbbd27b180f2d3791e1927df7899bcdd5f83dcc0c323a60b4230ebb9c61a222
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = true
12
+ insert_final_newline = true
@@ -0,0 +1,38 @@
1
+ # vim: ft=yaml
2
+
3
+ name: test
4
+ on: push
5
+ jobs:
6
+ rspec:
7
+ name: ruby-${{ matrix.ruby }}
8
+ runs-on: ubuntu-20.04
9
+
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby: ["3.1", "3.0", "2.7"]
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ bundler-cache: True
21
+
22
+ - run: bundle exec rspec --color
23
+
24
+ rubocop:
25
+ name: rubocop
26
+ runs-on: ubuntu-20.04
27
+
28
+ steps:
29
+ - uses: actions/checkout@v2
30
+ - uses: ruby/setup-ruby@v1
31
+ with:
32
+ ruby-version: 3.1
33
+ bundler-cache: True
34
+ env:
35
+ BUNDLE_JOBS: 4
36
+ BUNDLE_RETRY: 3
37
+
38
+ - run: bundle exec rubocop --parallel --color
data/.rubocop.yml ADDED
@@ -0,0 +1,9 @@
1
+ # .rubocop.yml
2
+
3
+ inherit_gem:
4
+ rubocop-config: default.yml
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.7
8
+ SuggestExtensions: False
9
+ NewCops: enable
data/CHANGELOG.md CHANGED
@@ -1,20 +1,36 @@
1
1
  # Changelog
2
+
2
3
  All notable changes to this project will be documented in this file.
3
4
 
4
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+
9
+ ## [1.2.0] - 2022-06-03
10
+
11
+ ### Added
12
+
13
+ - `run!` and `wait!` to raise error if any process exits with an error code != 0
14
+
15
+ ## [1.1.1] - 2020-12-21
16
+
8
17
  ### Fixed
18
+
9
19
  - Replaced deprecated `#with_clean_env` method
10
20
 
11
21
  ## [1.1.0] - 2020-11-19
22
+
12
23
  ### Added
24
+
13
25
  - Add support for IPv6 by using the hostname instead of the loopback IPv4 address (#2)
14
26
 
15
27
  ## 1.0.0 - 2019-05-13
28
+
16
29
  ### Fixed
30
+
17
31
  - Possible concurrent hash modification while iterating (#1)
18
32
 
19
- [Unreleased]: https://github.com/jgraichen/multi_process/compare/v1.1.0...HEAD
33
+ [unreleased]: https://github.com/jgraichen/multi_process/compare/v1.2.0...HEAD
34
+ [1.2.0]: https://github.com/jgraichen/multi_process/compare/v1.1.1...v1.2.0
35
+ [1.1.1]: https://github.com/jgraichen/multi_process/compare/v1.1.0...v1.1.1
20
36
  [1.1.0]: https://github.com/jgraichen/multi_process/compare/v1.0.0...v1.1.0
data/Gemfile CHANGED
@@ -1,7 +1,11 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
2
 
3
- gem 'rake'
4
- gem 'rspec', '>= 3.0.0.beta1'
3
+ source 'https://rubygems.org'
5
4
 
6
5
  # Specify your gem's dependencies in multi_process.gemspec
7
6
  gemspec
7
+
8
+ gem 'rake'
9
+ gem 'rake-release', '~> 1.3'
10
+ gem 'rspec', '~> 3.11'
11
+ gem 'rubocop-config', github: 'jgraichen/rubocop-config', ref: 'v9', require: false
data/README.md CHANGED
@@ -1,31 +1,35 @@
1
1
  # MultiProcess
2
2
 
3
- Handle multiple processes. Ruby >= 2.0.
4
-
5
- TODO: Just experiment.
3
+ Run multiple processes.
6
4
 
7
5
  ## Installation
8
6
 
9
7
  Add this line to your application's Gemfile:
10
8
 
11
- gem 'multi_process'
9
+ ```ruby
10
+ gem 'multi_process'
11
+ ```
12
12
 
13
13
  And then execute:
14
14
 
15
- $ bundle
15
+ ```
16
+ $ bundle
17
+ ```
16
18
 
17
19
  Or install it yourself as:
18
20
 
19
- $ gem install multi_process
21
+ ```
22
+ $ gem install multi_process
23
+ ```
20
24
 
21
25
  ## Usage
22
26
 
23
- ```
27
+ ```ruby
24
28
  receiver = MultiProcess::Logger $stdout, $stderr, sys: false
25
29
  group = MultiProcess::Group.new receiver: receiver
26
- group << MultiProcess::Process.new %w(ruby test.rb), title: 'rubyA'
27
- group << MultiProcess::Process.new %w(ruby test.rb), title: 'rubyB'
28
- group << MultiProcess::Process.new %w(ruby test.rb), title: 'rubyC'
30
+ group << MultiProcess::Process.new %w[ruby test.rb], title: 'rubyA'
31
+ group << MultiProcess::Process.new %w[ruby test.rb], title: 'rubyB'
32
+ group << MultiProcess::Process.new %w[ruby test.rb], title: 'rubyC'
29
33
  group.start # Start in background
30
34
  group.run # Block until finished
31
35
  group.wait # Wait until finished
@@ -43,7 +47,7 @@ group.stop # Stop processes
43
47
 
44
48
  ## Contributing
45
49
 
46
- 1. Fork it ( http://github.com/jgraichen/multi_process/fork )
50
+ 1. Fork it (http://github.com/jgraichen/multi_process/fork)
47
51
  2. Create your feature branch (`git checkout -b my-new-feature`)
48
52
  3. Commit your changes (`git commit -am 'Add some feature'`)
49
53
  4. Push to the branch (`git push origin my-new-feature`)
@@ -51,7 +55,7 @@ group.stop # Stop processes
51
55
 
52
56
  ## License
53
57
 
54
- Copyright (C) 2019 Jan Graichen
58
+ Copyright © 2019 Jan Graichen
55
59
 
56
60
  This program is free software: you can redistribute it and/or modify
57
61
  it under the terms of the GNU General Public License as published by
data/Rakefile CHANGED
@@ -1,4 +1,6 @@
1
- require 'bundler/gem_tasks'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/release'
2
4
  require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MultiProcess
4
+ class Error < StandardError; end
5
+
6
+ class ProcessError < Error
7
+ attr_reader :process
8
+
9
+ def initialize(process, *args, **kwargs)
10
+ @process = process
11
+ super(*args, **kwargs)
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiProcess
2
4
  #
3
5
  # Store and run a group of processes.
@@ -24,7 +26,7 @@ module MultiProcess
24
26
  #
25
27
  def initialize(receiver: nil, partition: nil)
26
28
  @processes = []
27
- @receiver = receiver ? receiver : MultiProcess::Logger.global
29
+ @receiver = receiver || MultiProcess::Logger.global
28
30
  @partition = partition ? partition.to_i : 0
29
31
  @mutex = Mutex.new
30
32
  end
@@ -87,6 +89,21 @@ module MultiProcess
87
89
  end
88
90
  end
89
91
 
92
+ # Wait until all process terminated.
93
+ #
94
+ # Raise an error if a process exists unsuccessfully.
95
+ #
96
+ # @param opts [ Hash ] Options.
97
+ # @option opts [ Integer ] :timeout Timeout in seconds to wait before raising {Timeout::Error}.
98
+ #
99
+ def wait!(timeout: nil)
100
+ if timeout
101
+ ::Timeout.timeout(timeout) { wait! }
102
+ else
103
+ processes.each(&:wait!)
104
+ end
105
+ end
106
+
90
107
  # Start all process and wait for them to terminate.
91
108
  #
92
109
  # Given options will be passed to {#start} and {#wait}.
@@ -96,17 +113,32 @@ module MultiProcess
96
113
  # when timeout error is raised.
97
114
  #
98
115
  def run(delay: nil, timeout: nil)
99
- if partition > 0
100
- partition.times.map do
101
- Thread.new do
102
- while (process = next_process)
103
- process.run
104
- end
105
- end
106
- end.each(&:join)
116
+ if partition.positive?
117
+ run_partition(&:run)
107
118
  else
108
- start delay: delay
109
- wait timeout: timeout
119
+ start(delay: delay)
120
+ wait(timeout: timeout)
121
+ end
122
+ ensure
123
+ stop
124
+ end
125
+
126
+ # Start all process and wait for them to terminate.
127
+ #
128
+ # Given options will be passed to {#start} and {#wait}. {#start}
129
+ # will only be called if partition is zero.
130
+ #
131
+ # If timeout is given process will be terminated using {#stop} when
132
+ # timeout error is raised.
133
+ #
134
+ # An error will be raised if any process exits unsuccessfully.
135
+ #
136
+ def run!(delay: nil, timeout: nil)
137
+ if partition.positive?
138
+ run_partition(&:run!)
139
+ else
140
+ start(delay: delay)
141
+ wait!(timeout: timeout)
110
142
  end
111
143
  ensure
112
144
  stop
@@ -151,5 +183,17 @@ module MultiProcess
151
183
  processes[@index - 1]
152
184
  end
153
185
  end
186
+
187
+ def run_partition
188
+ Array.new(partition) do
189
+ Thread.new do
190
+ Thread.current.report_on_exception = false
191
+
192
+ while (process = next_process)
193
+ yield process
194
+ end
195
+ end
196
+ end.each(&:join)
197
+ end
154
198
  end
155
199
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiProcess
2
4
  # Can create pipes and multiplex pipe content to put into
3
5
  # given IO objects e.g. multiple output from multiple
@@ -12,7 +14,7 @@ module MultiProcess
12
14
  # error sources.
13
15
  #
14
16
  def initialize(*args)
15
- @opts = Hash === args.last ? args.pop : {}
17
+ @opts = args.last.is_a?(Hash) ? args.pop : {}
16
18
  @out = args[0] || $stdout
17
19
  @err = args[1] || $stderr
18
20
 
@@ -25,12 +27,12 @@ module MultiProcess
25
27
 
26
28
  def received(process, name, line)
27
29
  case name
28
- when :err, :stderr
29
- output process, line, io: @err, delimiter: 'E>'
30
- when :out, :stdout
31
- output process, line
32
- when :sys
33
- output(process, line, delimiter: '$>') if @opts[:sys]
30
+ when :err, :stderr
31
+ output process, line, io: @err, delimiter: 'E>'
32
+ when :out, :stdout
33
+ output process, line
34
+ when :sys
35
+ output(process, line, delimiter: '$>') if @opts[:sys]
34
36
  end
35
37
  end
36
38
 
@@ -49,15 +51,13 @@ module MultiProcess
49
51
  private
50
52
 
51
53
  def output(process, line, opts = {})
52
- opts[:delimiter] ||= ' |'
54
+ opts[:delimiter] ||= ' |'
53
55
  name = if opts[:name]
54
56
  opts[:name].to_s.dup
57
+ elsif process
58
+ process.title.to_s.rjust(@colwidth, ' ')
55
59
  else
56
- if process
57
- process.title.to_s.rjust(@colwidth, ' ')
58
- else
59
- (' ' * @colwidth)
60
- end
60
+ (' ' * @colwidth)
61
61
  end
62
62
 
63
63
  io = opts[:io] || @out
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'nio'
2
4
 
3
5
  module MultiProcess
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiProcess
2
4
  # Receiver implementation that does nothing on every input.
3
5
  #
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class MultiProcess::Process
2
4
  # Provides functionality to wrap command in with bundle
3
5
  # execute.
4
6
  #
5
7
  module BundleExec
6
8
  def initialize(*args)
7
- opts = Hash === args.last ? args.pop : {}
8
- super %w(bundle exec) + args, opts
9
+ opts = args.last.is_a?(Hash) ? args.pop : {}
10
+ super %w[bundle exec] + args, opts
9
11
  end
10
12
  end
11
13
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class MultiProcess::Process
2
4
  # Provides functionality for a process that is a rails server
3
5
  # process.
@@ -12,21 +14,15 @@ class MultiProcess::Process
12
14
  #
13
15
  attr_reader :server
14
16
 
15
- # Port server should be running on.
16
- #
17
- # Default will be a free port determined when process is created.
18
- #
19
- attr_reader :port
20
-
21
17
  def initialize(opts = {})
22
18
  self.server = opts[:server] if opts[:server]
23
19
  self.port = opts[:port] if opts[:port]
24
20
 
25
- super *server_command, opts
21
+ super(*server_command, opts)
26
22
  end
27
23
 
28
24
  def server_command
29
- ['rails', 'server', server, '--port', port].reject(&:nil?).map(&:to_s)
25
+ ['rails', 'server', server, '--port', port].compact.map(&:to_s)
30
26
  end
31
27
 
32
28
  def server=(server)
@@ -34,7 +30,7 @@ class MultiProcess::Process
34
30
  end
35
31
 
36
32
  def port=(port)
37
- @port = port.to_i == 0 ? free_port : port.to_i
33
+ @port = port.to_i.zero? ? free_port : port.to_i
38
34
  end
39
35
 
40
36
  def port
@@ -42,7 +38,7 @@ class MultiProcess::Process
42
38
  end
43
39
 
44
40
  def available?
45
- fail ArgumentError.new "Cannot check availability for port #{port}." if port == 0
41
+ raise ArgumentError.new "Cannot check availability for port #{port}." if port.zero?
46
42
 
47
43
  TCPSocket.new('localhost', port).close
48
44
  true
@@ -70,7 +66,7 @@ class MultiProcess::Process
70
66
  socket.bind(Addrinfo.tcp('localhost', 0))
71
67
  socket.local_address.ip_port
72
68
  ensure
73
- socket.close if socket
69
+ socket&.close
74
70
  end
75
71
  end
76
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/module/delegation'
2
4
 
3
5
  module MultiProcess
@@ -20,14 +22,14 @@ module MultiProcess
20
22
 
21
23
  def initialize(*args)
22
24
  args.flatten!
23
- opts = (Hash === args.last ? args.pop : {})
25
+ opts = (args.last.is_a?(Hash) ? args.pop : {})
24
26
 
25
27
  @title = opts[:title].to_s || args.first.to_s.strip.split(/\s+/, 2)[0]
26
- @command = args.map { |arg| (arg =~ /\A[\s"']+\z/ ? arg.inspect : arg).gsub '"', '\"' }.join(' ')
27
- @childprocess = create_childprocess *args
28
+ @command = args.map {|arg| (/\A[\s"']+\z/.match?(arg) ? arg.inspect : arg).gsub '"', '\"' }.join(' ')
29
+ @childprocess = create_childprocess(*args)
28
30
 
29
- @env = opts[:env] if Hash === opts[:env]
30
- @env_clean = opts[:clean_env].nil? ? true : !!opts[:clean_env]
31
+ @env = opts[:env] if opts[:env].is_a?(Hash)
32
+ @env_clean = opts[:clean_env].nil? ? true : !opts[:clean_env].nil?
31
33
 
32
34
  self.receiver = opts[:receiver] || MultiProcess::Logger.global
33
35
 
@@ -54,6 +56,20 @@ module MultiProcess
54
56
  end
55
57
  end
56
58
 
59
+ # Wait until process finished.
60
+ #
61
+ # If no timeout is given it will wait definitely.
62
+ #
63
+ # @param opts [Hash] Options.
64
+ # @option opts [Integer] :timeout Timeout to wait in seconds.
65
+ #
66
+ def wait!(opts = {})
67
+ wait(opts)
68
+ return if exit_code.zero?
69
+
70
+ raise ::MultiProcess::ProcessError.new(self, "Process #{pid} exited with code #{exit_code}")
71
+ end
72
+
57
73
  # Start process.
58
74
  #
59
75
  # Started processes will be stopped when ruby VM exists by hooking into
@@ -63,7 +79,7 @@ module MultiProcess
63
79
  return false if started?
64
80
 
65
81
  at_exit { stop }
66
- receiver.message(self, :sys, command) if receiver
82
+ receiver&.message(self, :sys, command)
67
83
  start_childprocess
68
84
  @started = true
69
85
  end
@@ -73,7 +89,7 @@ module MultiProcess
73
89
  # Will call `ChildProcess#stop`.
74
90
  #
75
91
  def stop(*args)
76
- childprocess.stop *args if started?
92
+ childprocess.stop(*args) if started?
77
93
  end
78
94
 
79
95
  # Check if server is available. What available means can be defined
@@ -97,7 +113,7 @@ module MultiProcess
97
113
  Timeout.timeout timeout do
98
114
  sleep 0.2 until available?
99
115
  end
100
- rescue Timeout::Error => ex
116
+ rescue Timeout::Error
101
117
  raise Timeout::Error.new "Server #{id.inspect} on port #{port} didn't get up after #{timeout} seconds..."
102
118
  end
103
119
 
@@ -116,6 +132,15 @@ module MultiProcess
116
132
  wait opts
117
133
  end
118
134
 
135
+ # Start process and wait until it's finished.
136
+ #
137
+ # Given arguments will be passed to {#wait!}.
138
+ #
139
+ def run!(opts = {})
140
+ start
141
+ wait!(opts)
142
+ end
143
+
119
144
  # @!group Working Directory
120
145
 
121
146
  # Working directory for child process.
@@ -150,7 +175,8 @@ module MultiProcess
150
175
  # Set environment.
151
176
  #
152
177
  def env=(env)
153
- fail ArgumentError.new 'Environment must be a Hash.' unless hash === env
178
+ raise ArgumentError.new 'Environment must be a Hash.' unless env.is_a?(Hash)
179
+
154
180
  @env = env
155
181
  end
156
182
 
@@ -178,7 +204,7 @@ module MultiProcess
178
204
  # Create child process.
179
205
  #
180
206
  def create_childprocess(*args)
181
- ChildProcess.new *args.flatten
207
+ ChildProcess.new(*args.flatten)
182
208
  end
183
209
 
184
210
  # Start child process.
@@ -186,7 +212,7 @@ module MultiProcess
186
212
  # Can be used to hook in subclasses and modules.
187
213
  #
188
214
  def start_childprocess
189
- env.each { |k, v| childprocess.environment[k.to_s] = v.to_s }
215
+ env.each {|k, v| childprocess.environment[k.to_s] = v.to_s }
190
216
  childprocess.cwd = dir
191
217
 
192
218
  if clean_env?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiProcess
2
4
  # Can handle input from multiple processes and run custom
3
5
  # actions on event and output.
@@ -14,12 +16,12 @@ module MultiProcess
14
16
 
15
17
  Loop.instance.watch(reader) do |action, monitor|
16
18
  case action
17
- when :registered
18
- connected(process, name)
19
- when :ready
20
- received(process, name, read(monitor.io))
21
- when :eof
22
- removed(process, name)
19
+ when :registered
20
+ connected(process, name)
21
+ when :ready
22
+ received(process, name, read(monitor.io))
23
+ when :eof
24
+ removed(process, name)
23
25
  end
24
26
  end
25
27
 
@@ -40,7 +42,7 @@ module MultiProcess
40
42
  # Must be overridden by subclass.
41
43
  #
42
44
  def received(_process, _name, _message)
43
- fail NotImplementedError.new 'Subclass responsibility.'
45
+ raise NotImplementedError.new 'Subclass responsibility.'
44
46
  end
45
47
 
46
48
  # Read content from pipe. Can be used to provide custom reading
@@ -55,12 +57,10 @@ module MultiProcess
55
57
  # Called after pipe for process and name was removed because it
56
58
  # reached EOF.
57
59
  #
58
- def removed(_process, _name)
59
- end
60
+ def removed(_process, _name); end
60
61
 
61
62
  # Called after new pipe for process and name was created.
62
63
  #
63
- def connected(_process, _name)
64
- end
64
+ def connected(_process, _name); end
65
65
  end
66
66
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiProcess
2
4
  # Receiver implementation storing process output
3
5
  # in string.
@@ -9,7 +11,7 @@ module MultiProcess
9
11
 
10
12
  def get(name)
11
13
  @strings ||= {}
12
- @strings[name.to_s] ||= ''
14
+ @strings[name.to_s] ||= +''
13
15
  end
14
16
  end
15
17
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MultiProcess
2
4
  module VERSION
3
5
  MAJOR = 1
4
- MINOR = 1
5
- PATCH = 1
6
+ MINOR = 2
7
+ PATCH = 0
6
8
  STAGE = nil
7
- STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.')
9
+ STRING = [MAJOR, MINOR, PATCH, STAGE].compact.join('.')
8
10
 
9
11
  def self.to_s
10
12
  STRING
data/lib/multi_process.rb CHANGED
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'multi_process/version'
2
4
  require 'childprocess'
3
5
 
4
6
  module MultiProcess
5
7
  DEFAULT_TIMEOUT = 60
6
8
 
9
+ require 'multi_process/errors'
10
+
7
11
  require 'multi_process/loop'
8
12
  require 'multi_process/group'
9
13
  require 'multi_process/receiver'
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'multi_process/version'
5
6
 
@@ -13,8 +14,11 @@ Gem::Specification.new do |spec|
13
14
  spec.homepage = 'https://github.com/jgraichen/multi_process'
14
15
  spec.license = 'GPLv3'
15
16
 
17
+ spec.metadata['rubygems_mfa_required'] = 'true'
18
+ spec.required_ruby_version = '>= 2.7'
19
+
16
20
  spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f) }
18
22
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
23
  spec.require_paths = ['lib']
20
24
 
data/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "local>jgraichen/renovate-config"
5
+ ]
6
+ }
data/spec/files/env.rb CHANGED
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
 
2
- $stdout.puts "ENV: #{ENV[ARGV[0]]}"
3
+ $stdout.puts "ENV: #{ENV.fetch(ARGV[0], nil)}"
3
4
  $stdout.sync
4
5
 
5
6
  sleep 1
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ exit Integer(ARGV.fetch(0, '1'))
data/spec/files/sleep.rb CHANGED
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  sleep ARGV[0].to_i
data/spec/files/test.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  2.times do
3
4
  $stdout.puts "Output from #{ARGV[0]}"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MultiProcess::Group do
6
+ subject(:group) { MultiProcess::Group.new }
7
+
8
+ before do
9
+ group << MultiProcess::Process.new(command)
10
+ end
11
+
12
+ describe '#run!' do
13
+ context 'with failing command' do
14
+ let(:command) { %w[ruby spec/files/fail.rb] }
15
+
16
+ it 'does raise an error' do
17
+ expect { group.run! }.to raise_error(MultiProcess::ProcessError, /Process \d+ exited with code 1/)
18
+ end
19
+ end
20
+
21
+ context 'with partition and failing command' do
22
+ subject(:group) { MultiProcess::Group.new(partition: 1) }
23
+
24
+ let(:command) { %w[ruby spec/files/fail.rb] }
25
+
26
+ it 'does raise an error' do
27
+ expect { group.run! }.to raise_error(MultiProcess::ProcessError, /Process \d+ exited with code 1/)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MultiProcess::Process do
6
+ subject(:process) { MultiProcess::Process.new(command) }
7
+
8
+ describe '#run!' do
9
+ context 'with failing command' do
10
+ let(:command) { %w[ruby spec/files/fail.rb] }
11
+
12
+ it 'does raise an error' do
13
+ expect { process.run! }.to raise_error(MultiProcess::ProcessError, /Process \d+ exited with code 1/)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,62 +1,64 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe MultiProcess do
4
- it 'should run processes' do
6
+ it 'runs processes (I)' do
5
7
  reader, writer = IO.pipe
6
8
 
7
9
  logger = MultiProcess::Logger.new writer, collapse: false
8
10
  group = MultiProcess::Group.new receiver: logger
9
- group << MultiProcess::Process.new(%w(ruby spec/files/test.rb A), title: 'rubyA')
10
- group << MultiProcess::Process.new(%w(ruby spec/files/test.rb B), title: 'rubyBB')
11
- group << MultiProcess::Process.new(%w(ruby spec/files/test.rb C), title: 'rubyCCC')
11
+ group << MultiProcess::Process.new(%w[ruby spec/files/test.rb A], title: 'rubyA')
12
+ group << MultiProcess::Process.new(%w[ruby spec/files/test.rb B], title: 'rubyBB')
13
+ group << MultiProcess::Process.new(%w[ruby spec/files/test.rb C], title: 'rubyCCC')
12
14
  group.run
13
15
 
14
- expect(reader.read_nonblock(4096).split("\n")).to match_array <<-EOF.gsub(/^\s+\./, '').split("\n")
16
+ expect(reader.read_nonblock(4096).split("\n")).to match_array <<-OUTPUT.gsub(/^\s+\./, '').split("\n")
15
17
  . rubyBB | Output from B
16
18
  . rubyA | Output from A
17
19
  . rubyA | Output from A
18
20
  . rubyCCC | Output from C
19
21
  . rubyCCC | Output from C
20
22
  . rubyBB | Output from B
21
- EOF
23
+ OUTPUT
22
24
  end
23
25
 
24
- it 'should run processes' do
26
+ it 'runs processes (II)' do
25
27
  start = Time.now
26
28
 
27
29
  group = MultiProcess::Group.new receiver: MultiProcess::NilReceiver.new
28
- group << MultiProcess::Process.new(%w(ruby spec/files/sleep.rb 5000), title: 'rubyA')
29
- group << MultiProcess::Process.new(%w(ruby spec/files/sleep.rb 5000), title: 'rubyB')
30
- group << MultiProcess::Process.new(%w(ruby spec/files/sleep.rb 5000), title: 'rubyC')
30
+ group << MultiProcess::Process.new(%w[ruby spec/files/sleep.rb 5000], title: 'rubyA')
31
+ group << MultiProcess::Process.new(%w[ruby spec/files/sleep.rb 5000], title: 'rubyB')
32
+ group << MultiProcess::Process.new(%w[ruby spec/files/sleep.rb 5000], title: 'rubyC')
31
33
  group.start
32
34
  sleep 1
33
35
  group.stop
34
36
 
35
37
  group.processes.each do |p|
36
- expect(p).to_not be_alive
38
+ expect(p).not_to be_alive
37
39
  end
38
40
  expect(Time.now - start).to be < 2
39
41
  end
40
42
 
41
- it 'should partition processes' do
43
+ it 'partitions processes' do
42
44
  group = MultiProcess::Group.new partition: 4, receiver: MultiProcess::NilReceiver.new
43
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyA')
44
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyB')
45
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyC')
46
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyD')
47
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyE')
48
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyF')
49
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyG')
50
- group << MultiProcess::Process.new(%w(ruby sleep.rb 1), dir: 'spec/files', title: 'rubyH')
45
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyA')
46
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyB')
47
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyC')
48
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyD')
49
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyE')
50
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyF')
51
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyG')
52
+ group << MultiProcess::Process.new(%w[ruby sleep.rb 1], dir: 'spec/files', title: 'rubyH')
51
53
 
52
54
  start = Time.now
53
55
  group.run
54
- expect(Time.now - start).to be_within(0.3).of(2)
56
+ expect(Time.now - start).to be_within(0.5).of(2)
55
57
  end
56
58
 
57
- it 'should env processes' do
59
+ it 'envs processes' do
58
60
  receiver = MultiProcess::StringReceiver.new
59
- process = MultiProcess::Process.new(%w(ruby spec/files/env.rb TEST), env: { 'TEST' => 'abc' }, receiver: receiver)
61
+ process = MultiProcess::Process.new(%w[ruby spec/files/env.rb TEST], env: {'TEST' => 'abc'}, receiver: receiver)
60
62
  process.run
61
63
 
62
64
  expect(receiver.get(:out)).to eq "ENV: abc\n"
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rspec'
2
4
 
3
5
  require 'bundler'
@@ -5,7 +7,7 @@ Bundler.require
5
7
 
6
8
  require 'multi_process'
7
9
 
8
- Dir[File.expand_path('spec/support/**/*.rb')].each { |f| require f }
10
+ Dir[File.expand_path('spec/support/**/*.rb')].sort.each {|f| require f }
9
11
 
10
12
  RSpec.configure do |config|
11
13
  # ## Mock Framework
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multi_process
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Graichen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-21 00:00:00.000000000 Z
11
+ date: 2022-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -73,13 +73,17 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - ".editorconfig"
77
+ - ".github/workflows/test.yml"
76
78
  - ".gitignore"
79
+ - ".rubocop.yml"
77
80
  - CHANGELOG.md
78
81
  - Gemfile
79
82
  - LICENSE.txt
80
83
  - README.md
81
84
  - Rakefile
82
85
  - lib/multi_process.rb
86
+ - lib/multi_process/errors.rb
83
87
  - lib/multi_process/group.rb
84
88
  - lib/multi_process/logger.rb
85
89
  - lib/multi_process/loop.rb
@@ -91,15 +95,20 @@ files:
91
95
  - lib/multi_process/string_receiver.rb
92
96
  - lib/multi_process/version.rb
93
97
  - multi_process.gemspec
98
+ - renovate.json
94
99
  - spec/files/env.rb
100
+ - spec/files/fail.rb
95
101
  - spec/files/sleep.rb
96
102
  - spec/files/test.rb
103
+ - spec/multi_process/group_spec.rb
104
+ - spec/multi_process/process_spec.rb
97
105
  - spec/multi_process_spec.rb
98
106
  - spec/spec_helper.rb
99
107
  homepage: https://github.com/jgraichen/multi_process
100
108
  licenses:
101
109
  - GPLv3
102
- metadata: {}
110
+ metadata:
111
+ rubygems_mfa_required: 'true'
103
112
  post_install_message:
104
113
  rdoc_options: []
105
114
  require_paths:
@@ -108,20 +117,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
117
  requirements:
109
118
  - - ">="
110
119
  - !ruby/object:Gem::Version
111
- version: '0'
120
+ version: '2.7'
112
121
  required_rubygems_version: !ruby/object:Gem::Requirement
113
122
  requirements:
114
123
  - - ">="
115
124
  - !ruby/object:Gem::Version
116
125
  version: '0'
117
126
  requirements: []
118
- rubygems_version: 3.2.1
127
+ rubygems_version: 3.3.7
119
128
  signing_key:
120
129
  specification_version: 4
121
130
  summary: Handle multiple child processes.
122
131
  test_files:
123
132
  - spec/files/env.rb
133
+ - spec/files/fail.rb
124
134
  - spec/files/sleep.rb
125
135
  - spec/files/test.rb
136
+ - spec/multi_process/group_spec.rb
137
+ - spec/multi_process/process_spec.rb
126
138
  - spec/multi_process_spec.rb
127
139
  - spec/spec_helper.rb