nitra 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/bin/nitra +11 -1
  2. data/lib/nitra.rb +132 -41
  3. metadata +4 -4
data/bin/nitra CHANGED
@@ -6,7 +6,7 @@ require 'optparse'
6
6
  nitra = Nitra.new
7
7
 
8
8
  OptionParser.new do |opts|
9
- opts.banner = "Usage: nitra [options]"
9
+ opts.banner = "Usage: nitra [options] [spec_filename [...]]"
10
10
 
11
11
  opts.on("-c", "--cpus NUMBER", Integer, "Specify the number of CPUs to use, defaults to 4") do |n|
12
12
  nitra.process_count = n
@@ -24,6 +24,14 @@ OptionParser.new do |opts|
24
24
  nitra.migrate = true
25
25
  end
26
26
 
27
+ opts.on("-q", "--quiet", "Quiet; don't display progress bar") do
28
+ nitra.quiet = true
29
+ end
30
+
31
+ opts.on("-p", "--print-failures", "Print failures immediately when they occur") do
32
+ nitra.print_failures = true
33
+ end
34
+
27
35
  opts.on("--debug", "Print debug output") do
28
36
  nitra.debug = true
29
37
  end
@@ -34,4 +42,6 @@ OptionParser.new do |opts|
34
42
  end
35
43
  end.parse!
36
44
 
45
+ nitra.files = ARGV
46
+
37
47
  exit nitra.run
data/lib/nitra.rb CHANGED
@@ -1,47 +1,96 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+
1
4
  class Nitra
2
- attr_accessor :load_schema, :migrate, :debug
5
+ attr_accessor :load_schema, :migrate, :debug, :quiet, :print_failures, :fork_for_each_file
6
+ attr_accessor :files
3
7
  attr_accessor :process_count, :environment
4
8
 
5
9
  def initialize
6
10
  self.process_count = 4
7
11
  self.environment = "nitra"
12
+ self.fork_for_each_file = true
8
13
  end
9
14
 
10
15
  def run
11
16
  start_time = Time.now
12
17
  ENV["RAILS_ENV"] = environment
13
18
 
19
+ initialise_database
20
+
21
+ load_rails_environment
22
+
23
+ pipes = fork_workers
24
+
25
+ self.files = Dir["spec/**/*_spec.rb"] if files.nil? || files.empty?
26
+ return if files.empty?
27
+
28
+ trap("SIGTERM") { $aborted = true }
29
+ trap("SIGINT") { $aborted = true }
30
+
31
+ return_code, result = hand_out_files_to_workers(files, pipes)
32
+
33
+ trap("SIGTERM", "DEFAULT")
34
+ trap("SIGINT", "DEFAULT")
35
+
36
+ print_result(result)
37
+ puts "\n#{$aborted ? "Aborted after" : "Finished in"} #{"%0.1f" % (Time.now-start_time)} seconds" unless quiet
38
+
39
+ $aborted ? 255 : return_code
40
+ end
41
+
42
+ protected
43
+ def print_result(result)
44
+ puts result.gsub(/\n\n\n+/, "\n\n")
45
+ end
46
+
47
+ def print_progress
48
+ unless quiet
49
+ bar_length = @columns - 50
50
+ progress = @files_completed / @file_count.to_f
51
+ length_completed = (progress * bar_length).to_i
52
+ length_to_go = bar_length - length_completed
53
+ print "[#{"X" * length_completed}#{"." * length_to_go}] #{@files_completed}/#{@file_count} (#{"%0.1f%%" % (progress*100)}) * #{@example_count} examples, #{@failure_count} failures\r"
54
+ end
55
+ end
56
+
57
+ def initialise_database
14
58
  if load_schema
15
59
  process_count.times do |index|
16
- puts "initialising database #{index+1}..."
60
+ puts "initialising database #{index+1}..." unless quiet
17
61
  ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
18
- system("bundle exec rake db:schema:load")
62
+ system("bundle exec rake db:drop db:create db:schema:load")
19
63
  end
20
64
  end
21
65
 
22
66
  if migrate
23
67
  process_count.times do |index|
24
- puts "migrating database #{index+1}..."
68
+ puts "migrating database #{index+1}..." unless quiet
25
69
  ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
26
70
  system("bundle exec rake db:migrate")
27
71
  end
28
72
  end
73
+ end
29
74
 
75
+ def load_rails_environment
30
76
  puts "loading rails environment..." if debug
31
77
 
32
78
  ENV["TEST_ENV_NUMBER"] = "1"
33
79
 
34
80
  require 'spec/spec_helper'
35
- require 'stringio'
36
81
 
37
82
  ActiveRecord::Base.connection.disconnect!
83
+ end
38
84
 
39
- trap("SIGINT") { Process.kill("SIGKILL", Process.pid) }
40
-
41
- pipes = (0...process_count).collect do |index|
85
+ def fork_workers
86
+ (0...process_count).collect do |index|
42
87
  server_sender_pipe = IO.pipe
43
88
  client_sender_pipe = IO.pipe
89
+
44
90
  fork do
91
+ trap("SIGTERM") { Process.kill("SIGKILL", Process.pid) }
92
+ trap("SIGINT") { Process.kill("SIGKILL", Process.pid) }
93
+
45
94
  server_sender_pipe[1].close
46
95
  client_sender_pipe[0].close
47
96
  rd = server_sender_pipe[0]
@@ -49,14 +98,38 @@ class Nitra
49
98
 
50
99
  ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
51
100
 
101
+ # Find the database config for this TEST_ENV_NUMBER and manually initialise a connection.
52
102
  database_config = YAML.load(ERB.new(IO.read("#{Rails.root}/config/database.yml")).result)[ENV["RAILS_ENV"]]
53
103
  ActiveRecord::Base.establish_connection(database_config)
54
104
  Rails.cache.reset if Rails.cache.respond_to?(:reset)
55
105
 
106
+ # RSpec doesn't like it when you change the IO between invocations. So we make one object and flush it
107
+ # after every invocation.
108
+ io = StringIO.new
109
+
110
+ # When rspec processes the first spec file, it does initialisation like loading in fixtures into the
111
+ # database. If we're forking for each file, we need to initialise first so it doesn't try to initialise
112
+ # for every single file.
113
+ if fork_for_each_file
114
+ puts "running empty spec to make rspec run its initialisation" if debug
115
+ file = Tempfile.new("nitra")
116
+ begin
117
+ file.write("require 'spec_helper'; describe('nitra preloading') { it('preloads the fixtures') { 1.should == 1 } }\n")
118
+ file.close
119
+ RSpec::Core::CommandLine.new(["-f", "p", file.path]).run(io, io)
120
+ ensure
121
+ file.close unless file.closed?
122
+ file.unlink
123
+ end
124
+ RSpec.reset
125
+ io.string = ""
126
+ end
127
+
128
+ # OK, we're good to receive requests. Tell our master.
56
129
  puts "announcing availability" if debug
57
130
  wr.write("0,0\n")
58
131
 
59
- io = StringIO.new
132
+ # Loop until our master tells us we're finished.
60
133
  loop do
61
134
  puts "#{index} waiting for next job" if debug
62
135
  filename = rd.gets
@@ -64,26 +137,38 @@ class Nitra
64
137
  filename = filename.chomp
65
138
  puts "#{index} starting to process #{filename}" if debug
66
139
 
67
- result = RSpec::Core::CommandLine.new(["-f", "p", filename]).run(io, io)
68
- RSpec.reset
140
+ perform_rspec_for_filename = lambda do
141
+ begin
142
+ result = RSpec::Core::CommandLine.new(["-f", "p", filename]).run(io, io)
143
+ rescue LoadError
144
+ io << "\nCould not load file #{filename}\n\n"
145
+ result = 1
146
+ end
69
147
 
70
- puts "#{index} #{filename} processed" if debug
148
+ wr.write("#{result.to_i},#{io.string.length}\n#{io.string}")
149
+ end
71
150
 
72
- wr.write("#{result.to_i},#{io.string.length}\n#{io.string}")
73
- io.string = ""
151
+ if fork_for_each_file
152
+ pid = fork(&perform_rspec_for_filename)
153
+ Process.wait(pid) if pid
154
+ else
155
+ perform_rspec_for_filename.call
156
+ io.string = ""
157
+ RSpec.reset
158
+ end
159
+
160
+ puts "#{index} #{filename} processed" if debug
74
161
  end
75
162
  end
163
+
76
164
  server_sender_pipe[0].close
77
165
  client_sender_pipe[1].close
78
166
  [client_sender_pipe[0], server_sender_pipe[1]]
79
167
  end
168
+ end
80
169
 
81
- readers = pipes.collect(&:first)
82
- files = Dir["spec/**/*_spec.rb"]
83
-
84
- return if files.empty?
85
-
86
- puts "Running rspec on #{files.length} files spread across #{process_count} processes\n\n"
170
+ def hand_out_files_to_workers(files, pipes)
171
+ puts "Running rspec on #{files.length} files spread across #{process_count} processes\n\n" unless quiet
87
172
 
88
173
  @columns = (ENV['COLUMNS'] || 120).to_i
89
174
  @file_count = files.length
@@ -93,27 +178,45 @@ class Nitra
93
178
 
94
179
  result = ""
95
180
  worst_return_code = 0
181
+ readers = pipes.collect(&:first)
96
182
 
97
- while readers.length > 0
183
+ while !$aborted && readers.length > 0
98
184
  print_progress
99
185
  fds = IO.select(readers)
100
186
  fds.first.each do |fd|
101
- return_code, length = fd.gets.split(",")
187
+ unless value = fd.gets
188
+ readers.delete(fd)
189
+ worst_return_code = 255
190
+ if readers.empty?
191
+ puts "Worker unexpectedly died. No more workers to run specs - dying."
192
+ else
193
+ puts "Worker unexpectedly died. Trying to continue with fewer workers."
194
+ end
195
+ break
196
+ end
102
197
 
198
+ return_code, length = value.split(",")
103
199
  worst_return_code = return_code.to_i if worst_return_code < return_code.to_i
104
200
 
105
- if length.nil?
106
- break
107
- elsif length.to_i > 0
201
+ if length.to_i > 0
108
202
  data = fd.read(length.to_i)
109
203
 
110
204
  @files_completed += 1
205
+ failure_count = 0
206
+
111
207
  if m = data.match(/(\d+) examples?, (\d+) failure/)
112
208
  @example_count += m[1].to_i
113
- @failure_count += m[2].to_i
209
+ failure_count += m[2].to_i
114
210
  end
115
211
 
116
- result << data.gsub(/^[.FP]+$/, '').gsub(/\nFailed examples:.+/m, '').gsub(/^Finished in.+$/, '').gsub(/^\d+ example.+$/, '').gsub(/^No examples found.$/, '').gsub(/^Failures:$/, '')
212
+ @failure_count += failure_count
213
+ stripped_data = data.gsub(/^[.FP*]+$/, '').gsub(/\nFailed examples:.+/m, '').gsub(/^Finished in.+$/, '').gsub(/^\d+ example.+$/, '').gsub(/^No examples found.$/, '').gsub(/^Failures:$/, '')
214
+
215
+ if print_failures && failure_count > 0
216
+ print_result(stripped_data)
217
+ else
218
+ result << stripped_data
219
+ end
117
220
  else
118
221
  puts "ZERO LENGTH" if debug
119
222
  end
@@ -130,20 +233,8 @@ class Nitra
130
233
  end
131
234
 
132
235
  print_progress
133
- puts ""
134
- result = result.gsub(/\n\n\n+/, "\n\n")
135
- puts result
136
- puts "\nFinished in #{"%0.1f" % (Time.now-start_time)} seconds"
236
+ puts "" unless quiet
137
237
 
138
- worst_return_code
139
- end
140
-
141
- protected
142
- def print_progress
143
- bar_length = @columns - 50
144
- progress = @files_completed / @file_count.to_f
145
- length_completed = (progress * bar_length).to_i
146
- length_to_go = bar_length - length_completed
147
- print "[#{"X" * length_completed}#{"." * length_to_go}] #{@files_completed}/#{@file_count} (#{"%0.1f%%" % (progress*100)}) * #{@example_count} examples, #{@failure_count} failures\r"
238
+ [worst_return_code, result]
148
239
  end
149
240
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nitra
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 2
10
- version: 0.9.2
9
+ - 3
10
+ version: 0.9.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Roger Nesbitt
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-20 00:00:00 +13:00
18
+ date: 2012-06-07 00:00:00 +12:00
19
19
  default_executable:
20
20
  dependencies: []
21
21