nitra 0.9.2 → 0.9.3

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