multi_process 1.1.1 → 1.2.0

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: 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