nitra 0.9.3 → 0.9.4

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 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: []