nitra 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
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