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.
- data/bin/nitra +11 -1
- data/lib/nitra.rb +132 -41
- 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
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
148
|
+
wr.write("#{result.to_i},#{io.string.length}\n#{io.string}")
|
149
|
+
end
|
71
150
|
|
72
|
-
|
73
|
-
|
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
|
-
|
82
|
-
files
|
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
|
-
|
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.
|
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
|
-
|
209
|
+
failure_count += m[2].to_i
|
114
210
|
end
|
115
211
|
|
116
|
-
|
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:
|
4
|
+
hash: 61
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
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-
|
18
|
+
date: 2012-06-07 00:00:00 +12:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|