nitra 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
data/bin/nitra CHANGED
@@ -1,47 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'nitra'
4
- require 'optparse'
5
4
 
6
- nitra = Nitra.new
7
-
8
- OptionParser.new do |opts|
9
- opts.banner = "Usage: nitra [options] [spec_filename [...]]"
10
-
11
- opts.on("-c", "--cpus NUMBER", Integer, "Specify the number of CPUs to use, defaults to 4") do |n|
12
- nitra.process_count = n
13
- end
14
-
15
- opts.on("-e", "--environment STRING", String, "The Rails environment to use, defaults to 'nitra'") do |environment|
16
- nitra.environment = environment
17
- end
18
-
19
- opts.on("--load", "Load schema into database before running specs") do
20
- nitra.load_schema = true
21
- end
22
-
23
- opts.on("--migrate", "Migrate database before running specs") do
24
- nitra.migrate = true
25
- end
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
-
35
- opts.on("--debug", "Print debug output") do
36
- nitra.debug = true
37
- end
38
-
39
- opts.on_tail("-h", "--help", "Show this message") do
40
- puts opts
41
- exit
42
- end
43
- end.parse!
44
-
45
- nitra.files = ARGV
46
-
47
- exit nitra.run
5
+ configuration = Nitra::Configuration.new
6
+ Nitra::CommandLine.new(configuration, ARGV)
7
+ if configuration.slave_mode
8
+ Nitra::Slave::Server.new.run
9
+ else
10
+ exit Nitra::Client.new(configuration, ARGV).run ? 0 : 1
11
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ class Nitra::Channel
4
+ ProtocolInvalidError = Class.new(StandardError)
5
+
6
+ attr_reader :rd, :wr
7
+ attr_accessor :raise_epipe_on_write_error
8
+
9
+ def initialize(rd, wr)
10
+ @rd = rd
11
+ @wr = wr
12
+ end
13
+
14
+ def self.pipe
15
+ c_rd, s_wr = IO.pipe
16
+ s_rd, c_wr = IO.pipe
17
+ [new(c_rd, c_wr), new(s_rd, s_wr)]
18
+ end
19
+
20
+ def self.read_select(channels)
21
+ fds = IO.select(channels.collect(&:rd))
22
+ fds.first.collect do |fd|
23
+ channels.detect {|c| c.rd == fd}
24
+ end
25
+ end
26
+
27
+ def close
28
+ rd.close
29
+ wr.close
30
+ end
31
+
32
+ def read
33
+ return unless line = rd.gets
34
+ if result = line.strip.match(/\ANITRA,(\d+)\z/)
35
+ data = rd.read(result[1].to_i)
36
+ YAML.load(data)
37
+ else
38
+ raise ProtocolInvalidError, "Expected nitra length line, got #{line.inspect}"
39
+ end
40
+ end
41
+
42
+ def write(data)
43
+ encoded = YAML.dump(data)
44
+ wr.write("NITRA,#{encoded.length}\n#{encoded}")
45
+ rescue Errno::EPIPE
46
+ raise if raise_epipe_on_write_error
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ class Nitra::Client
2
+ attr_reader :configuration, :files
3
+
4
+ def initialize(configuration, files = nil)
5
+ @configuration = configuration
6
+ @files = files
7
+ @columns = (ENV['COLUMNS'] || 120).to_i
8
+ end
9
+
10
+ def run
11
+ start_time = Time.now
12
+
13
+ master = Nitra::Master.new(configuration, files)
14
+ progress = master.run do |progress, data|
15
+ print_progress(progress)
16
+ if data && configuration.print_failures && data["failure_count"] != 0
17
+ puts unless configuration.quiet
18
+ puts "=== output for #{data["filename"]} #{'='*40}"
19
+ puts data["text"].gsub(/\n\n\n+/, "\n\n")
20
+ end
21
+ end
22
+
23
+ puts progress.output.gsub(/\n\n\n+/, "\n\n")
24
+
25
+ puts "\n#{progress.files_completed}/#{progress.file_count} files processed, #{progress.example_count} examples, #{progress.failure_count} failures"
26
+ puts "#{$aborted ? "Aborted after" : "Finished in"} #{"%0.1f" % (Time.now-start_time)} seconds" unless configuration.quiet
27
+
28
+ !$aborted && progress.files_completed == progress.file_count && progress.failure_count.zero?
29
+ end
30
+
31
+ protected
32
+ def print_progress(progress)
33
+ return if configuration.quiet
34
+
35
+ bar_length = @columns - 50
36
+ progress_factor = progress.files_completed / progress.file_count.to_f
37
+ length_completed = (progress_factor * bar_length).to_i
38
+ length_to_go = bar_length - length_completed
39
+ print "\r[#{"X" * length_completed}#{"." * length_to_go}] #{progress.files_completed}/#{progress.file_count} (#{"%0.1f%%" % (progress_factor*100)}) * #{progress.example_count} examples, #{progress.failure_count} failures\r"
40
+ puts if configuration.debug
41
+ $stdout.flush
42
+ end
43
+ end
@@ -0,0 +1,54 @@
1
+ require 'optparse'
2
+
3
+ class Nitra::CommandLine
4
+ attr_reader :configuration
5
+
6
+ def initialize(configuration, argv)
7
+ @configuration = configuration
8
+
9
+ OptionParser.new(argv) do |opts|
10
+ opts.banner = "Usage: nitra [options] [spec_filename [...]]"
11
+
12
+ opts.on("-c", "--cpus NUMBER", Integer, "Specify the number of CPUs to use on the host, or if specified after a --slave, on the slave") do |n|
13
+ configuration.set_process_count n
14
+ end
15
+
16
+ opts.on("-e", "--environment STRING", String, "The Rails environment to use, defaults to 'nitra'") do |environment|
17
+ configuration.environment = environment
18
+ end
19
+
20
+ opts.on("--load", "Load schema into database before running specs") do
21
+ configuration.load_schema = true
22
+ end
23
+
24
+ opts.on("--migrate", "Migrate database before running specs") do
25
+ configuration.migrate = true
26
+ end
27
+
28
+ opts.on("-q", "--quiet", "Quiet; don't display progress bar") do
29
+ configuration.quiet = true
30
+ end
31
+
32
+ opts.on("-p", "--print-failures", "Print failures immediately when they occur") do
33
+ configuration.print_failures = true
34
+ end
35
+
36
+ opts.on("--slave CONNECTION_COMMAND", String, "Provide a command that executes \"nitra --slave-mode\" on another host") do |connection_command|
37
+ configuration.slaves << {:command => connection_command, :cpus => nil}
38
+ end
39
+
40
+ opts.on("--slave-mode", "Run in slave mode; ignores all other command-line options") do
41
+ configuration.slave_mode = true
42
+ end
43
+
44
+ opts.on("--debug", "Print debug output") do
45
+ configuration.debug = true
46
+ end
47
+
48
+ opts.on_tail("-h", "--help", "Show this message") do
49
+ puts opts
50
+ exit
51
+ end
52
+ end.parse!
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ class Nitra::Configuration
2
+ attr_accessor :load_schema, :migrate, :debug, :quiet, :print_failures, :fork_for_each_file
3
+ attr_accessor :process_count, :environment, :slaves, :slave_mode
4
+
5
+ def initialize
6
+ self.environment = "nitra"
7
+ self.fork_for_each_file = true
8
+ self.slaves = []
9
+ end
10
+
11
+ def calculate_default_process_count
12
+ self.process_count ||= Nitra::Utils.processor_count
13
+ end
14
+
15
+ def set_process_count(n)
16
+ if slaves.empty?
17
+ self.process_count = n
18
+ else
19
+ slaves.last[:cpus] = n
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
1
+ class Nitra::Master
2
+ attr_reader :configuration, :files
3
+
4
+ def initialize(configuration, files = nil)
5
+ @configuration = configuration
6
+ @files = files
7
+ end
8
+
9
+ def run
10
+ @files = Dir["spec/**/*_spec.rb"] if files.nil? || files.empty?
11
+ return if files.empty?
12
+
13
+ progress = Nitra::Progress.new
14
+ progress.file_count = @files.length
15
+ yield progress, nil
16
+
17
+ runners = []
18
+
19
+ if configuration.process_count > 0
20
+ client, runner = Nitra::Channel.pipe
21
+ fork do
22
+ runner.close
23
+ Nitra::Runner.new(configuration, client, "A").run
24
+ end
25
+ client.close
26
+ runners << runner
27
+ end
28
+
29
+ slave = Nitra::Slave::Client.new(configuration)
30
+ runners += slave.connect("")
31
+
32
+ while runners.length > 0
33
+ Nitra::Channel.read_select(runners).each do |channel|
34
+ if data = channel.read
35
+ case data["command"]
36
+ when "next"
37
+ channel.write "filename" => files.shift
38
+ when "result"
39
+ progress.files_completed += 1
40
+ progress.example_count += data["example_count"] || 0
41
+ progress.failure_count += data["failure_count"] || 0
42
+ progress.output << data["text"]
43
+ yield progress, data
44
+ when "debug"
45
+ if configuration.debug
46
+ puts "[DEBUG] #{data["text"]}"
47
+ end
48
+ when "stdout"
49
+ if configuration.debug
50
+ puts "STDOUT for #{data["process"]} #{data["filename"]}:\n#{data["text"]}" unless data["text"].empty?
51
+ end
52
+ end
53
+ else
54
+ runners.delete channel
55
+ end
56
+ end
57
+ end
58
+
59
+ debug "waiting for all children to exit..."
60
+ Process.waitall
61
+ progress
62
+ end
63
+
64
+ protected
65
+ def debug(*text)
66
+ puts "master: #{text.join}" if configuration.debug
67
+ end
68
+ end
@@ -0,0 +1,8 @@
1
+ class Nitra::Progress
2
+ attr_accessor :file_count, :files_completed, :example_count, :failure_count, :output
3
+
4
+ def initialize
5
+ @file_count = @files_completed = @example_count = @failure_count = 0
6
+ @output = ""
7
+ end
8
+ end
@@ -0,0 +1,127 @@
1
+ require 'stringio'
2
+
3
+ class Nitra::Runner
4
+ attr_reader :configuration, :server_channel, :runner_id
5
+
6
+ def initialize(configuration, server_channel, runner_id)
7
+ @configuration = configuration
8
+ @server_channel = server_channel
9
+ @runner_id = runner_id
10
+
11
+ configuration.calculate_default_process_count
12
+ server_channel.raise_epipe_on_write_error = true
13
+ end
14
+
15
+ def run
16
+ ENV["RAILS_ENV"] = configuration.environment
17
+
18
+ initialise_database
19
+
20
+ load_rails_environment
21
+
22
+ pipes = start_workers
23
+
24
+ trap("SIGTERM") { $aborted = true }
25
+ trap("SIGINT") { $aborted = true }
26
+
27
+ hand_out_files_to_workers(pipes)
28
+ rescue Errno::EPIPE
29
+ ensure
30
+ trap("SIGTERM", "DEFAULT")
31
+ trap("SIGINT", "DEFAULT")
32
+ end
33
+
34
+ protected
35
+ def initialise_database
36
+ if configuration.load_schema
37
+ configuration.process_count.times do |index|
38
+ debug "initialising database #{index+1}..."
39
+ ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
40
+ output = `bundle exec rake db:drop db:create db:schema:load 2>&1`
41
+ server_channel.write("command" => "stdout", "process" => "db:schema:load", "text" => output)
42
+ end
43
+ end
44
+
45
+ if configuration.migrate
46
+ configuration.process_count.times do |index|
47
+ debug "migrating database #{index+1}..."
48
+ ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
49
+ output = `bundle exec rake db:migrate 2>&1`
50
+ server_channel.write("command" => "stdout", "process" => "db:migrate", "text" => output)
51
+ end
52
+ end
53
+ end
54
+
55
+ def load_rails_environment
56
+ debug "loading rails environment..."
57
+
58
+ ENV["TEST_ENV_NUMBER"] = "1"
59
+
60
+ output = Nitra::Utils.capture_output do
61
+ require 'spec/spec_helper'
62
+ end
63
+
64
+ server_channel.write("command" => "stdout", "process" => "rails initialisation", "text" => output)
65
+
66
+ ActiveRecord::Base.connection.disconnect!
67
+ end
68
+
69
+ def start_workers
70
+ (0...configuration.process_count).collect do |index|
71
+ Nitra::Worker.new(runner_id, index, configuration).fork_and_run
72
+ end
73
+ end
74
+
75
+ def hand_out_files_to_workers(pipes)
76
+ while !$aborted && pipes.length > 0
77
+ Nitra::Channel.read_select(pipes).each do |worker_channel|
78
+ unless data = worker_channel.read
79
+ pipes.delete worker_channel
80
+ debug "worker #{worker_channel} unexpectedly died."
81
+ next
82
+ end
83
+
84
+ case data['command']
85
+ when "debug", "stdout"
86
+ server_channel.write(data)
87
+
88
+ when "result"
89
+ if m = data['text'].match(/(\d+) examples?, (\d+) failure/)
90
+ example_count = m[1].to_i
91
+ failure_count = m[2].to_i
92
+ end
93
+
94
+ stripped_data = data['text'].gsub(/^[.FP*]+$/, '').gsub(/\nFailed examples:.+/m, '').gsub(/^Finished in.+$/, '').gsub(/^\d+ example.+$/, '').gsub(/^No examples found.$/, '').gsub(/^Failures:$/, '')
95
+
96
+ server_channel.write(
97
+ "command" => "result",
98
+ "filename" => data["filename"],
99
+ "return_code" => data["return_code"],
100
+ "example_count" => example_count,
101
+ "failure_count" => failure_count,
102
+ "text" => stripped_data)
103
+
104
+ when "ready"
105
+ server_channel.write("command" => "next")
106
+ next_file = server_channel.read.fetch("filename")
107
+
108
+ if next_file
109
+ debug "sending #{next_file} to channel #{worker_channel}"
110
+ worker_channel.write "command" => "process", "filename" => next_file
111
+ else
112
+ debug "sending close message to channel #{worker_channel}"
113
+ worker_channel.write "command" => "close"
114
+ pipes.delete worker_channel
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def debug(*text)
122
+ server_channel.write(
123
+ "command" => "debug",
124
+ "text" => "runner #{runner_id}: #{text.join}"
125
+ ) if configuration.debug
126
+ end
127
+ end
@@ -0,0 +1,71 @@
1
+ module Nitra::Slave
2
+ class Client
3
+ attr_reader :configuration
4
+
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+ end
8
+
9
+ def connect(runner_id_base)
10
+ runner_id = "#{runner_id_base}A"
11
+
12
+ @configuration.slaves.collect do |slave_details|
13
+ runner_id = runner_id.succ
14
+ connect_host(slave_details, runner_id)
15
+ end.compact
16
+ end
17
+
18
+ protected
19
+ def connect_host(slave_details, runner_id)
20
+ client, server = Nitra::Channel.pipe
21
+
22
+ puts "Starting slave runner #{runner_id} with command '#{slave_details[:command]}'" if configuration.debug
23
+
24
+ pid = fork do
25
+ server.close
26
+ $stdin.reopen(client.rd)
27
+ $stdout.reopen(client.wr)
28
+ $stderr.reopen(client.wr)
29
+ exec slave_details[:command]
30
+ end
31
+ client.close
32
+
33
+ slave_config = configuration.dup
34
+ slave_config.process_count = slave_details.fetch(:cpus)
35
+
36
+ server.write(
37
+ "command" => "configuration",
38
+ "runner_id" => runner_id,
39
+ "configuration" => slave_config)
40
+ response = server.read
41
+
42
+ if response["command"] == "connected"
43
+ puts "Connection to slave runner #{runner_id} successful" if configuration.debug
44
+ server
45
+ else
46
+ $stderr.puts "Connection to slave runner #{runner_id} FAILED with message: #{response.inspect}"
47
+ Process.kill("KILL", pid)
48
+ nil
49
+ end
50
+ end
51
+ end
52
+
53
+ class Server
54
+ attr_reader :channel
55
+
56
+ def run
57
+ @channel = Nitra::Channel.new($stdin, $stdout)
58
+
59
+ response = @channel.read
60
+ unless response && response["command"] == "configuration"
61
+ puts "handshake failed"
62
+ exit 1
63
+ end
64
+
65
+ @channel.write("command" => "connected")
66
+
67
+ runner = Nitra::Runner.new(response["configuration"], channel, response["runner_id"])
68
+ runner.run
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ class Nitra::Utils
2
+ # The following taken and modified from the 'parallel' gem.
3
+ # Licensed under the MIT licence, copyright Michael Grosser.
4
+ def self.processor_count
5
+ @processor_count ||= case `uname`
6
+ when /darwin/i
7
+ (`which hwprefs` != '' ? `hwprefs thread_count` : `sysctl -n hw.ncpu`).to_i
8
+ when /linux/i
9
+ `grep -c processor /proc/cpuinfo`.to_i
10
+ when /freebsd/i
11
+ `sysctl -n hw.ncpu`.to_i
12
+ when /solaris2/i
13
+ `psrinfo -p`.to_i # this is physical cpus afaik
14
+ else
15
+ 1
16
+ end
17
+ end
18
+
19
+ def self.capture_output
20
+ old_stdout = $stdout
21
+ old_stderr = $stderr
22
+ $stdout = $stderr = io = StringIO.new
23
+ begin
24
+ yield
25
+ ensure
26
+ $stdout = old_stdout
27
+ $stderr = old_stderr
28
+ end
29
+ io.string
30
+ end
31
+ end
@@ -0,0 +1,128 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+
4
+ class Nitra::Worker
5
+ attr_reader :runner_id, :worker_number, :configuration, :channel
6
+
7
+ def initialize(runner_id, worker_number, configuration)
8
+ @runner_id = runner_id
9
+ @worker_number = worker_number
10
+ @configuration = configuration
11
+ end
12
+
13
+ def fork_and_run
14
+ client, server = Nitra::Channel.pipe
15
+
16
+ fork do
17
+ server.close
18
+ @channel = client
19
+ run
20
+ end
21
+
22
+ client.close
23
+ server
24
+ end
25
+
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)
55
+ 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
60
+ end
61
+ RSpec.reset
62
+ io.string = ""
63
+ end
64
+
65
+ # Loop until our master tells us we're finished.
66
+ loop do
67
+ debug "announcing availability"
68
+ channel.write("command" => "ready")
69
+
70
+ debug "waiting for next job"
71
+ data = channel.read
72
+ if data.nil? || data["command"] == "close"
73
+ debug "channel closed, exiting"
74
+ exit
75
+ end
76
+
77
+ filename = data.fetch("filename").chomp
78
+ debug "starting to process #{filename}"
79
+
80
+ perform_rspec_for_filename = lambda do
81
+ 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
86
+ end
87
+
88
+ channel.write("command" => "result", "filename" => filename, "return_code" => result.to_i, "text" => io.string)
89
+ end
90
+
91
+ if configuration.fork_for_each_file
92
+ rd, wr = IO.pipe
93
+ pid = fork do
94
+ rd.close
95
+ $stdout.reopen(wr)
96
+ $stderr.reopen(wr)
97
+ perform_rspec_for_filename.call
98
+ end
99
+ wr.close
100
+ stdout_buffer = ""
101
+ loop do
102
+ IO.select([rd])
103
+ text = rd.read
104
+ break if text.nil? || text.length.zero?
105
+ stdout_buffer << text
106
+ end
107
+ 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
115
+ end
116
+ channel.write("command" => "stdout", "process" => "rspec", "filename" => filename, "text" => stdout_buffer) unless stdout_buffer.empty?
117
+
118
+ debug "#{filename} processed"
119
+ end
120
+ end
121
+
122
+ def debug(*text)
123
+ channel.write(
124
+ "command" => "debug",
125
+ "text" => "worker #{runner_id}.#{worker_number}: #{text.join}"
126
+ ) if configuration.debug
127
+ end
128
+ end
data/lib/nitra.rb CHANGED
@@ -1,240 +1,13 @@
1
- require 'stringio'
2
- require 'tempfile'
3
-
4
- class Nitra
5
- attr_accessor :load_schema, :migrate, :debug, :quiet, :print_failures, :fork_for_each_file
6
- attr_accessor :files
7
- attr_accessor :process_count, :environment
8
-
9
- def initialize
10
- self.process_count = 4
11
- self.environment = "nitra"
12
- self.fork_for_each_file = true
13
- end
14
-
15
- def run
16
- start_time = Time.now
17
- ENV["RAILS_ENV"] = environment
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
58
- if load_schema
59
- process_count.times do |index|
60
- puts "initialising database #{index+1}..." unless quiet
61
- ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
62
- system("bundle exec rake db:drop db:create db:schema:load")
63
- end
64
- end
65
-
66
- if migrate
67
- process_count.times do |index|
68
- puts "migrating database #{index+1}..." unless quiet
69
- ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
70
- system("bundle exec rake db:migrate")
71
- end
72
- end
73
- end
74
-
75
- def load_rails_environment
76
- puts "loading rails environment..." if debug
77
-
78
- ENV["TEST_ENV_NUMBER"] = "1"
79
-
80
- require 'spec/spec_helper'
81
-
82
- ActiveRecord::Base.connection.disconnect!
83
- end
84
-
85
- def fork_workers
86
- (0...process_count).collect do |index|
87
- server_sender_pipe = IO.pipe
88
- client_sender_pipe = IO.pipe
89
-
90
- fork do
91
- trap("SIGTERM") { Process.kill("SIGKILL", Process.pid) }
92
- trap("SIGINT") { Process.kill("SIGKILL", Process.pid) }
93
-
94
- server_sender_pipe[1].close
95
- client_sender_pipe[0].close
96
- rd = server_sender_pipe[0]
97
- wr = client_sender_pipe[1]
98
-
99
- ENV["TEST_ENV_NUMBER"] = (index + 1).to_s
100
-
101
- # Find the database config for this TEST_ENV_NUMBER and manually initialise a connection.
102
- database_config = YAML.load(ERB.new(IO.read("#{Rails.root}/config/database.yml")).result)[ENV["RAILS_ENV"]]
103
- ActiveRecord::Base.establish_connection(database_config)
104
- Rails.cache.reset if Rails.cache.respond_to?(:reset)
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.
129
- puts "announcing availability" if debug
130
- wr.write("0,0\n")
131
-
132
- # Loop until our master tells us we're finished.
133
- loop do
134
- puts "#{index} waiting for next job" if debug
135
- filename = rd.gets
136
- exit if filename.blank?
137
- filename = filename.chomp
138
- puts "#{index} starting to process #{filename}" if debug
139
-
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
147
-
148
- wr.write("#{result.to_i},#{io.string.length}\n#{io.string}")
149
- end
150
-
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
161
- end
162
- end
163
-
164
- server_sender_pipe[0].close
165
- client_sender_pipe[1].close
166
- [client_sender_pipe[0], server_sender_pipe[1]]
167
- end
168
- end
169
-
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
172
-
173
- @columns = (ENV['COLUMNS'] || 120).to_i
174
- @file_count = files.length
175
- @files_completed = 0
176
- @example_count = 0
177
- @failure_count = 0
178
-
179
- result = ""
180
- worst_return_code = 0
181
- readers = pipes.collect(&:first)
182
-
183
- while !$aborted && readers.length > 0
184
- print_progress
185
- fds = IO.select(readers)
186
- fds.first.each do |fd|
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
197
-
198
- return_code, length = value.split(",")
199
- worst_return_code = return_code.to_i if worst_return_code < return_code.to_i
200
-
201
- if length.to_i > 0
202
- data = fd.read(length.to_i)
203
-
204
- @files_completed += 1
205
- failure_count = 0
206
-
207
- if m = data.match(/(\d+) examples?, (\d+) failure/)
208
- @example_count += m[1].to_i
209
- failure_count += m[2].to_i
210
- end
211
-
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
220
- else
221
- puts "ZERO LENGTH" if debug
222
- end
223
-
224
- wr = pipes.detect {|rd, wr| rd == fd}[1]
225
- if files.length.zero?
226
- wr.puts ""
227
- readers.delete(fd)
228
- else
229
- puts "master is sending #{files.first} to fd #{wr}" if debug
230
- wr.puts files.shift
231
- end
232
- end
233
- end
234
-
235
- print_progress
236
- puts "" unless quiet
237
-
238
- [worst_return_code, result]
239
- end
1
+ module Nitra
240
2
  end
3
+
4
+ require 'nitra/channel'
5
+ require 'nitra/client'
6
+ require 'nitra/command_line'
7
+ require 'nitra/configuration'
8
+ require 'nitra/master'
9
+ require 'nitra/progress'
10
+ require 'nitra/runner'
11
+ require 'nitra/slave'
12
+ require 'nitra/utils'
13
+ require 'nitra/worker'
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: 61
4
+ hash: 51
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 3
10
- version: 0.9.3
9
+ - 4
10
+ version: 0.9.4
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-06-07 00:00:00 +12:00
18
+ date: 2012-06-08 00:00:00 +12:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -31,6 +31,16 @@ files:
31
31
  - README
32
32
  - lib/nitra.rb
33
33
  - bin/nitra
34
+ - lib/nitra/channel.rb
35
+ - lib/nitra/client.rb
36
+ - lib/nitra/command_line.rb
37
+ - lib/nitra/configuration.rb
38
+ - lib/nitra/master.rb
39
+ - lib/nitra/progress.rb
40
+ - lib/nitra/runner.rb
41
+ - lib/nitra/slave.rb
42
+ - lib/nitra/utils.rb
43
+ - lib/nitra/worker.rb
34
44
  has_rdoc: true
35
45
  homepage:
36
46
  licenses: []