nitra 0.9.4 → 0.9.5

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.
data/lib/nitra/worker.rb CHANGED
@@ -1,128 +1,211 @@
1
1
  require 'stringio'
2
2
  require 'tempfile'
3
3
 
4
- class Nitra::Worker
5
- attr_reader :runner_id, :worker_number, :configuration, :channel
4
+ module Nitra
5
+ module Workers
6
+ class Worker
7
+ class << self
6
8
 
7
- def initialize(runner_id, worker_number, configuration)
8
- @runner_id = runner_id
9
- @worker_number = worker_number
10
- @configuration = configuration
11
- end
9
+ @@worker_classes = {}
12
10
 
13
- def fork_and_run
14
- client, server = Nitra::Channel.pipe
11
+ def inherited(klass)
12
+ @@worker_classes[klass.framework_name] = klass
13
+ end
15
14
 
16
- fork do
17
- server.close
18
- @channel = client
19
- run
20
- end
15
+ def worker_classes
16
+ @@worker_classes
17
+ end
21
18
 
22
- client.close
23
- server
24
- end
19
+ ##
20
+ # Return the framework name of this worker
21
+ #
22
+ def framework_name
23
+ self.name.split("::").last.downcase
24
+ end
25
+ end
26
+
27
+
28
+ attr_reader :runner_id, :worker_number, :configuration, :channel, :io
29
+
30
+ def initialize(runner_id, worker_number, configuration)
31
+ @runner_id = runner_id
32
+ @worker_number = worker_number
33
+ @configuration = configuration
34
+ @forked_worker_pid = nil
35
+
36
+ ENV["TEST_ENV_NUMBER"] = worker_number.to_s
37
+
38
+ # Frameworks don't like it when you change the IO between invocations.
39
+ # So we make one object and flush it after every invocation.
40
+ @io = StringIO.new
41
+ end
25
42
 
26
- protected
27
- def run
28
- trap("SIGTERM") { Process.kill("SIGKILL", Process.pid) }
29
- trap("SIGINT") { Process.kill("SIGKILL", Process.pid) }
30
-
31
- debug "started"
32
-
33
- ENV["TEST_ENV_NUMBER"] = (worker_number + 1).to_s
34
-
35
- # Find the database config for this TEST_ENV_NUMBER and manually initialise a connection.
36
- database_config = YAML.load(ERB.new(IO.read("#{Rails.root}/config/database.yml")).result)[ENV["RAILS_ENV"]]
37
- ActiveRecord::Base.establish_connection(database_config)
38
- Rails.cache.reset if Rails.cache.respond_to?(:reset)
39
-
40
- # RSpec doesn't like it when you change the IO between invocations. So we make one object and flush it
41
- # after every invocation.
42
- io = StringIO.new
43
-
44
- # When rspec processes the first spec file, it does initialisation like loading in fixtures into the
45
- # database. If we're forking for each file, we need to initialise first so it doesn't try to initialise
46
- # for every single file.
47
- if configuration.fork_for_each_file
48
- debug "running empty spec to make rspec run its initialisation"
49
- file = Tempfile.new("nitra")
50
- begin
51
- file.write("require 'spec_helper'; describe('nitra preloading') { it('preloads the fixtures') { 1.should == 1 } }\n")
52
- file.close
53
- output = Nitra::Utils.capture_output do
54
- RSpec::Core::CommandLine.new(["-f", "p", file.path]).run(io, io)
43
+
44
+ def fork_and_run
45
+ client, server = Nitra::Channel.pipe
46
+
47
+ pid = fork do
48
+ # This is important. We don't want anything bubbling up to the master that we didn't send there.
49
+ # We reopen later to get the output from the framework run.
50
+ $stdout.reopen('/dev/null', 'a')
51
+ $stderr.reopen('/dev/null', 'a')
52
+
53
+ trap("USR1") { interrupt_forked_worker_and_exit }
54
+
55
+ server.close
56
+ @channel = client
57
+ begin
58
+ run
59
+ rescue => e
60
+ channel.write("command" => "error", "process" => "init framework", "text" => e.message, "worker_number" => worker_number)
61
+ end
55
62
  end
56
- channel.write("command" => "stdout", "process" => "init rspec", "text" => output) unless output.empty?
57
- ensure
58
- file.close unless file.closed?
59
- file.unlink
63
+
64
+ client.close
65
+
66
+ [pid, server]
60
67
  end
61
- RSpec.reset
62
- io.string = ""
63
- end
64
68
 
65
- # Loop until our master tells us we're finished.
66
- loop do
67
- debug "announcing availability"
68
- channel.write("command" => "ready")
69
+ protected
70
+ def load_environment
71
+ raise 'Subclasses must impliment this method.'
72
+ end
69
73
 
70
- debug "waiting for next job"
71
- data = channel.read
72
- if data.nil? || data["command"] == "close"
73
- debug "channel closed, exiting"
74
- exit
74
+ def minimal_file
75
+ raise 'Subclasses must impliment this method.'
76
+ end
77
+
78
+ def run_file(filename, preload = false)
79
+ raise 'Subclasses must impliment this method.'
75
80
  end
76
81
 
77
- filename = data.fetch("filename").chomp
78
- debug "starting to process #{filename}"
82
+ def clean_up
83
+ raise 'Subclasses must impliment this method.'
84
+ end
85
+
86
+ def run
87
+ trap("SIGTERM") do
88
+ channel.write("command" => "error", "process" => "trap", "text" => 'Received SIGTERM', "worker_number" => worker_number)
89
+ Process.kill("SIGKILL", Process.pid)
90
+ end
91
+ trap("SIGINT") do
92
+ channel.write("command" => "error", "process" => "trap", "text" => 'Received SIGINT', "worker_number" => worker_number)
93
+ Process.kill("SIGKILL", Process.pid)
94
+ end
95
+
96
+ debug "Started, using TEST_ENV_NUMBER #{ENV['TEST_ENV_NUMBER']}"
97
+ connect_to_database
98
+ reset_cache
99
+
100
+ preload_framework
101
+
102
+ # Loop until our runner passes us a message from the master to tells us we're finished.
103
+ loop do
104
+ debug "Announcing availability"
105
+ channel.write("command" => "ready", "framework" => self.class.framework_name, "worker_number" => worker_number)
106
+ debug "Waiting for next job"
107
+ data = channel.read
108
+ if data.nil? || data["command"] == "close"
109
+ debug "Channel closed, exiting"
110
+ exit
111
+ elsif data['command'] == "process"
112
+ filename = data["filename"].chomp
113
+ process_file(filename)
114
+ end
115
+ end
116
+ end
79
117
 
80
- perform_rspec_for_filename = lambda do
118
+ def preload_framework
119
+ debug "running empty spec/feature to make framework run its initialisation"
120
+ file = Tempfile.new("nitra")
81
121
  begin
82
- result = RSpec::Core::CommandLine.new(["-f", "p", filename]).run(io, io)
83
- rescue LoadError
84
- io << "\nCould not load file #{filename}\n\n"
85
- result = 1
122
+ load_environment
123
+ file.write(minimal_file)
124
+ file.close
125
+
126
+ output = Nitra::Utils.capture_output do
127
+ run_file(file.path, true)
128
+ end
129
+
130
+ channel.write("command" => "stdout", "process" => "init framework", "text" => output, "worker_number" => worker_number) unless output.empty?
131
+ ensure
132
+ file.close unless file.closed?
133
+ file.unlink
134
+ io.string = ""
86
135
  end
136
+ clean_up
137
+ end
87
138
 
88
- channel.write("command" => "result", "filename" => filename, "return_code" => result.to_i, "text" => io.string)
139
+ def connect_to_database
140
+ if defined?(Rails)
141
+ Nitra::RailsTooling.connect_to_database
142
+ debug("Connected to database #{ActiveRecord::Base.connection.current_database}")
89
143
  end
144
+ end
145
+
146
+ def reset_cache
147
+ Nitra::RailsTooling.reset_cache if defined?(Rails)
148
+ end
149
+
150
+ ##
151
+ # Process the file, forking before hand.
152
+ #
153
+ # There's two sets of data we're interested in, the output from the test framework, and any other output.
154
+ # 1) We capture the framework's output in the @io object and send that up to the runner in a results message.
155
+ # This happens in the run_x_file methods.
156
+ # 2) Anything else we capture off the stdout/stderr using the pipe and fire off in the stdout message.
157
+ #
158
+ def process_file(filename)
159
+ debug "Starting to process #{filename}"
160
+ start_time = Time.now
90
161
 
91
- if configuration.fork_for_each_file
92
162
  rd, wr = IO.pipe
93
- pid = fork do
94
- rd.close
163
+ @forked_worker_pid = fork do
164
+ trap('USR1') { exit! } # at_exit hooks will be run in the parent.
95
165
  $stdout.reopen(wr)
96
166
  $stderr.reopen(wr)
97
- perform_rspec_for_filename.call
167
+ rd.close
168
+ $0 = filename
169
+ run_file(filename)
170
+ wr.close
171
+ exit! # at_exit hooks will be run in the parent.
98
172
  end
99
173
  wr.close
100
- stdout_buffer = ""
174
+ output = ""
101
175
  loop do
102
176
  IO.select([rd])
103
177
  text = rd.read
104
178
  break if text.nil? || text.length.zero?
105
- stdout_buffer << text
179
+ output.concat text
106
180
  end
107
181
  rd.close
108
- Process.wait(pid) if pid
109
- else
110
- stdout_buffer = Nitra::Utils.capture_output do
111
- perform_rspec_for_filename.call
112
- end
113
- io.string = ""
114
- RSpec.reset
182
+ Process.wait(@forked_worker_pid) if @forked_worker_pid
183
+
184
+ @forked_worker_pid = nil
185
+
186
+ end_time = Time.now
187
+ channel.write("command" => "stdout", "process" => "test framework", "filename" => filename, "text" => output, "worker_number" => worker_number) unless output.empty?
188
+ debug "#{filename} processed in #{'%0.2f' % (end_time - start_time)}s"
115
189
  end
116
- channel.write("command" => "stdout", "process" => "rspec", "filename" => filename, "text" => stdout_buffer) unless stdout_buffer.empty?
117
190
 
118
- debug "#{filename} processed"
119
- end
120
- end
121
191
 
122
- def debug(*text)
123
- channel.write(
124
- "command" => "debug",
125
- "text" => "worker #{runner_id}.#{worker_number}: #{text.join}"
126
- ) if configuration.debug
192
+ ##
193
+ # Interrupts the forked worker cleanly and exits
194
+ #
195
+ def interrupt_forked_worker_and_exit
196
+ Process.kill('USR1', @forked_worker_pid) if @forked_worker_pid
197
+ Process.waitall
198
+ exit
199
+ end
200
+
201
+ ##
202
+ # Sends debug data up to the runner.
203
+ #
204
+ def debug(*text)
205
+ if configuration.debug
206
+ channel.write("command" => "debug", "text" => "worker #{runner_id}.#{worker_number}: #{text.join}", "worker_number" => worker_number)
207
+ end
208
+ end
209
+ end
127
210
  end
128
211
  end
@@ -0,0 +1,62 @@
1
+ module Nitra::Workers
2
+ class Cucumber < Worker
3
+ def self.files
4
+ Dir["features/**/*.feature"].sort_by {|f| File.size(f)}.reverse
5
+ end
6
+
7
+ def self.filename_match?(filename)
8
+ filename =~ /\.feature/
9
+ end
10
+
11
+ def initialize(runner_id, worker_number, configuration)
12
+ super(runner_id, worker_number, configuration)
13
+ end
14
+
15
+ def load_environment
16
+ require 'cucumber'
17
+ require 'nitra/ext/cucumber'
18
+ end
19
+
20
+ def minimal_file
21
+ <<-EOS
22
+ Feature: cucumber preloading
23
+ Scenario: a fake scenario
24
+ Given every step is unimplemented
25
+ When we run this file
26
+ Then Cucumber will load it's environment
27
+ EOS
28
+ end
29
+
30
+ ##
31
+ # Run a Cucumber file and write the results back to the runner.
32
+ #
33
+ # Doesn't write back to the runner if we mark the run as preloading.
34
+ #
35
+ def run_file(filename, preloading = false)
36
+ @cuke_runtime ||= ::Cucumber::ResetableRuntime.new # This runtime gets reused, this is important as it's the part that loads the steps...
37
+ begin
38
+ result = 1
39
+ cuke_config = ::Cucumber::Cli::Configuration.new(io, io)
40
+ cuke_config.parse!(["--no-color", "--require", "features", filename])
41
+ @cuke_runtime.configure(cuke_config)
42
+ @cuke_runtime.run!
43
+ result = 0 unless @cuke_runtime.results.failure?
44
+ rescue LoadError => e
45
+ io << "\nCould not load file #{filename}: #{e.message}\n\n"
46
+ rescue Exception => e
47
+ io << "Exception when running #{filename}: #{e.message}"
48
+ io << e.backtrace[0..7].join("\n")
49
+ end
50
+
51
+ if preloading
52
+ puts(io.string)
53
+ else
54
+ channel.write("command" => "result", "filename" => filename, "return_code" => result.to_i, "text" => io.string, "worker_number" => worker_number)
55
+ end
56
+ end
57
+
58
+ def clean_up
59
+ @cuke_runtime.reset
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,65 @@
1
+ module Nitra::Workers
2
+ class Rspec < Worker
3
+ def self.files
4
+ Dir["spec/**/*_spec.rb"].sort_by {|f| File.size(f)}.reverse
5
+ end
6
+
7
+ def self.filename_match?(filename)
8
+ filename =~ /_spec\.rb/
9
+ end
10
+
11
+ def initialize(runner_id, worker_number, configuration)
12
+ super(runner_id, worker_number, configuration)
13
+ end
14
+
15
+ def load_environment
16
+ require 'rspec'
17
+ RSpec::Core::Runner.disable_autorun!
18
+ end
19
+
20
+ def minimal_file
21
+ <<-EOS
22
+ require 'spec_helper'
23
+ describe('nitra preloading') do
24
+ it('preloads the fixtures') do
25
+ expect(1).to eq(1)
26
+ end
27
+ end
28
+ EOS
29
+ end
30
+
31
+ ##
32
+ # Run an rspec file and write the results back to the runner.
33
+ #
34
+ # Doesn't write back to the runner if we mark the run as preloading.
35
+ #
36
+ def run_file(filename, preloading = false)
37
+ begin
38
+ result = RSpec::Core::CommandLine.new(["-f", "p", filename]).run(io, io)
39
+ rescue LoadError => e
40
+ io << "\nCould not load file #{filename}: #{e.message}\n\n"
41
+ result = 1
42
+ rescue Exception => e
43
+ io << "Exception when running #{filename}: #{e.message}"
44
+ io << e.backtrace[0..7].join("\n")
45
+ result = 1
46
+ end
47
+
48
+ if preloading
49
+ puts io.string
50
+ else
51
+ channel.write("command" => "result", "filename" => filename, "return_code" => result.to_i, "text" => io.string, "worker_number" => worker_number)
52
+ end
53
+ end
54
+
55
+ def clean_up
56
+ # Rspec.reset in 2.6 didn't destroy your rspec_rails fixture loading, we can't use it anymore for it's intended purpose.
57
+ # This means our world object will be slightly polluted by the preload_framework code, but that's a small price to pay
58
+ # to upgrade.
59
+ #
60
+ # RSpec.reset
61
+ #
62
+ RSpec.instance_variable_set(:@world, nil)
63
+ end
64
+ end
65
+ end
data/lib/nitra.rb CHANGED
@@ -2,12 +2,16 @@ module Nitra
2
2
  end
3
3
 
4
4
  require 'nitra/channel'
5
- require 'nitra/client'
6
5
  require 'nitra/command_line'
7
6
  require 'nitra/configuration'
8
7
  require 'nitra/master'
8
+ require 'nitra/formatter'
9
9
  require 'nitra/progress'
10
10
  require 'nitra/runner'
11
+ require 'nitra/tasks'
11
12
  require 'nitra/slave'
12
13
  require 'nitra/utils'
13
14
  require 'nitra/worker'
15
+ require 'nitra/workers/cucumber'
16
+ require 'nitra/workers/rspec'
17
+ require 'nitra/rails_tooling'
@@ -0,0 +1,44 @@
1
+ gem 'minitest'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
4
+ require_relative '../../lib/nitra/channel'
5
+
6
+ describe Nitra::Channel do
7
+ describe ".pipe" do
8
+ it "creates a pipe pair" do
9
+ server, client = Nitra::Channel.pipe
10
+ server.must_be_instance_of Nitra::Channel
11
+ client.must_be_instance_of Nitra::Channel
12
+ end
13
+ end
14
+
15
+ describe "#close" do
16
+ it "closes channels" do
17
+ server, client = Nitra::Channel.pipe
18
+ server.close
19
+ server.rd.must_be :closed?
20
+ server.wr.must_be :closed?
21
+ end
22
+ end
23
+
24
+ describe "#write" do
25
+ it "writes a NITRA encoded yaml message" do
26
+ server, client = Nitra::Channel.pipe
27
+ server.write(['encode all the things'])
28
+ client.read.must_equal ['encode all the things']
29
+ end
30
+ end
31
+
32
+ describe "#read" do
33
+ it "reads NITRA encoded yaml messages" do
34
+ server, client = Nitra::Channel.pipe
35
+ client.write(['encode all the things'])
36
+ server.read.must_equal ['encode all the things']
37
+ end
38
+ it "rejects bad messages" do
39
+ server, client = Nitra::Channel.pipe
40
+ client.wr.write("not a nitra packet\n")
41
+ proc {server.read}.must_raise Nitra::Channel::ProtocolInvalidError
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,133 @@
1
+ gem 'minitest'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
4
+ require_relative '../../lib/nitra/command_line'
5
+
6
+ describe Nitra::CommandLine do
7
+
8
+ let(:config){ m = MiniTest::Mock.new; m.expect(:set_default_framework, []); m}
9
+
10
+ describe "option parsing" do
11
+ describe "-c" do
12
+ it "sets process count" do
13
+ config.expect(:set_process_count, nil, [2])
14
+ Nitra::CommandLine.new(config, ['-c','2'])
15
+ config.verify
16
+ end
17
+ end
18
+
19
+ describe "--cucumber" do
20
+ it "adds cucumber to framework" do
21
+ config.expect(:add_framework, nil, ['cucumber'])
22
+ Nitra::CommandLine.new(config, ['--cucumber'])
23
+ config.verify
24
+ end
25
+ end
26
+
27
+ describe "--debug" do
28
+ it "adds debug flag" do
29
+ config.expect(:debug=, nil, [true])
30
+ Nitra::CommandLine.new(config, ['--debug'])
31
+ config.verify
32
+ end
33
+ end
34
+
35
+ describe "-p" do
36
+ it "adds print failure flag" do
37
+ config.expect(:print_failures=, nil, [true])
38
+ Nitra::CommandLine.new(config, ['-p'])
39
+ config.verify
40
+ end
41
+ end
42
+
43
+ describe "-q" do
44
+ it "adds quiet flag" do
45
+ config.expect(:quiet=, nil, [true])
46
+ Nitra::CommandLine.new(config, ['-q'])
47
+ config.verify
48
+ end
49
+ end
50
+
51
+ describe "-e" do
52
+ it "sets the rails environment" do
53
+ config.expect(:environment=, nil, ["test"])
54
+ Nitra::CommandLine.new(config, ["-e", "test"])
55
+ config.verify
56
+ end
57
+ end
58
+
59
+ describe "--rake-after-runner" do
60
+ it "adds rake tasks to run after runner finishes" do
61
+ config.expect(:add_rake_task, nil, [:after_runner, ['list:of','rake:tasks']])
62
+ Nitra::CommandLine.new(config, ['--rake-after-runner', 'list:of,rake:tasks'])
63
+ config.verify
64
+ end
65
+ end
66
+
67
+ describe "--rake-before-runner" do
68
+ it "adds rake tasks to run before runner starts" do
69
+ config.expect(:add_rake_task, nil, [:before_runner, ['list:of','rake:tasks']])
70
+ Nitra::CommandLine.new(config, ['--rake-before-runner', 'list:of,rake:tasks'])
71
+ config.verify
72
+ end
73
+ end
74
+
75
+ describe "--rake-before-worker" do
76
+ it "adds rake tasks to run before worker starts" do
77
+ config.expect(:add_rake_task, nil, [:before_worker, ['list:of','rake:tasks']])
78
+ Nitra::CommandLine.new(config, ['--rake-before-worker', 'list:of,rake:tasks'])
79
+ config.verify
80
+ end
81
+ end
82
+
83
+ describe "-r" do
84
+ it "adds the db:reset rake task to run before worker starts" do
85
+ config.expect(:add_rake_task, nil, [:before_worker, ['db:reset']])
86
+ Nitra::CommandLine.new(config, ['-r'])
87
+ config.verify
88
+ end
89
+ end
90
+
91
+ describe "--reset" do
92
+ it "adds the db:reset rake task to run before worker starts" do
93
+ config.expect(:add_rake_task, nil, [:before_worker, ['db:reset']])
94
+ Nitra::CommandLine.new(config, ['--reset'])
95
+ config.verify
96
+ end
97
+ end
98
+
99
+ describe "--rspec" do
100
+ it "adds rspec to framework" do
101
+ config.expect(:add_framework, nil, ['rspec'])
102
+ Nitra::CommandLine.new(config, ['--rspec'])
103
+ config.verify
104
+ end
105
+ end
106
+
107
+ describe "--slave-mode" do
108
+ it "turns on slave mode" do
109
+ config.expect(:slave_mode=, nil, [true])
110
+ Nitra::CommandLine.new(config, ['--slave-mode'])
111
+ config.verify
112
+ end
113
+ end
114
+
115
+ describe "--slave" do
116
+ it "adds a command that will be run later as a slave" do
117
+ config.expect(:add_slave, nil, ['the command to run'])
118
+ Nitra::CommandLine.new(config, ['--slave', 'the command to run'])
119
+ config.verify
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "file lists" do
125
+ it "parses out options and leavs only files in list" do
126
+ argv = ['--slave','the slave command','this_test_file_spec.rb']
127
+ config.expect(:add_slave, nil, ['the slave command'])
128
+ Nitra::CommandLine.new(config, argv)
129
+ config.verify
130
+ argv.must_equal ['this_test_file_spec.rb']
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,60 @@
1
+ gem 'minitest'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
4
+ require_relative '../../lib/nitra/configuration'
5
+
6
+ describe Nitra::Configuration do
7
+ let(:config){ Nitra::Configuration.new }
8
+ it "has default values" do
9
+ config.slaves.must_equal []
10
+ config.frameworks.must_equal []
11
+ config.rake_tasks.must_equal Hash.new;
12
+ config.process_count.must_equal Nitra::Utils.processor_count
13
+ end
14
+
15
+ describe "#add_framework" do
16
+ it "adds a framework to frameworks" do
17
+ config.add_framework("cucumber")
18
+ config.add_framework("rspec")
19
+ config.frameworks.must_equal ["cucumber", "rspec"]
20
+ end
21
+ end
22
+
23
+ describe "#add_rake_task" do
24
+ it "adds a rake task to the rake task hash" do
25
+ config.add_rake_task(:task_name, ['list','of','tasks'])
26
+ config.rake_tasks.must_equal({:task_name => ['list','of','tasks']})
27
+ end
28
+ end
29
+
30
+ describe "#add_slave" do
31
+ it "adds a slave command to the slave array" do
32
+ command = 'command to run to get a nitra slave'
33
+ config.add_slave(command)
34
+ config.slaves[0].must_equal({:command => command, :cpus => nil})
35
+ end
36
+ end
37
+
38
+ describe "#set_default_framework" do
39
+ it "sets the default framework to the first one in the list" do
40
+ config.add_framework 'rspec'
41
+ config.add_framework 'cucumber'
42
+ config.set_default_framework
43
+ config.framework.must_equal 'rspec'
44
+ end
45
+
46
+ it "does nothing when there's no frameworks" do
47
+ config.set_default_framework
48
+ config.framework.must_be_nil
49
+ end
50
+ end
51
+
52
+ # We want slaves to inherit all config except for process count.
53
+ # This needs refactoring to not be so frickin retardedk.
54
+ it "does interesting things with slave process configs" do
55
+ config.process_count.must_equal Nitra::Utils.processor_count
56
+ config.slaves << {}
57
+ config.set_process_count 1000
58
+ config.slaves.first[:cpus].must_equal 1000
59
+ end
60
+ end