This repository has been archived by the owner on Apr 2, 2024. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8d4459a
Showing
18 changed files
with
869 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.gem | ||
.bundle | ||
Gemfile.lock | ||
pkg/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source "http://rubygems.org" | ||
|
||
# Specify your gem's dependencies in rails_parallel.gemspec | ||
gemspec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
rails_parallel | ||
============== | ||
|
||
rails_parallel makes your Rails tests scale with the number of CPU cores available. | ||
|
||
It also speeds up the testing process in general, by making heavy use of forking to only have to load the Rails environment once. | ||
|
||
Installation | ||
------------ | ||
|
||
To load rails_parallel, require "rails_parallel/rake" early in your Rakefile. One possibility is to load it conditionally based on an environment variable: | ||
|
||
require 'rails_parallel/rake' if ENV['PARALLEL'] | ||
|
||
You'll want to add a lib/tasks/rails_parallel.rake with at least the following: | ||
|
||
# RailsParallel handles the DB schema. | ||
Rake::Task['test:prepare'].clear_prerequisites if Object.const_get(:RailsParallel) | ||
|
||
namespace :parallel do | ||
# Run this task if you have non-test tasks to run first and you want the | ||
# RailsParallel worker to start loading your environment earlier. | ||
task :launch do | ||
RailsParallel::Rake.launch | ||
end | ||
|
||
# RailsParallel runs this if it needs to reload the DB. | ||
namespace :db do | ||
task :setup => ['db:drop', 'db:create', 'db:schema:load'] | ||
end | ||
end | ||
|
||
This gem was designed as an internal project and currently makes certain assumptions about your project setup, such as the use of MySQL and a separate versioned schema (rather than db/schema.rb). These will become more generic in future versions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require 'bundler' | ||
Bundler::GemHelper.install_tasks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/env ruby | ||
|
||
ENV['RAILS_ENV'] = 'test' | ||
|
||
begin | ||
puts 'RP: Loading RailsParallel.' | ||
$LOAD_PATH << 'lib' | ||
require 'rails_parallel/runner' | ||
require 'rails_parallel/object_socket' | ||
|
||
socket = ObjectSocket.new(IO.for_fd(ARGV.first.to_i)) | ||
socket << :started | ||
|
||
puts 'RP: Loading Rails.' | ||
require "#{ENV['RAILS_PARALLEL_ROOT']}/config/environment" | ||
|
||
puts 'RP: Ready for testing.' | ||
RailsParallel::Runner.launch(socket) | ||
puts 'RP: Shutting down.' | ||
Kernel.exit!(0) | ||
rescue Interrupt, SignalException | ||
Kernel.exit!(1) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module RailsParallel | ||
# Nothing here. Require 'rails_parallel/rake' in your Rakefile if you want RP. | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
require 'test/unit/collector' | ||
|
||
module RailsParallel | ||
class Collector | ||
include Test::Unit::Collector | ||
|
||
NAME = 'collected from the ObjectSpace' | ||
|
||
def prepare(timings, test_name) | ||
@suites = {} | ||
::ObjectSpace.each_object(Class) do |klass| | ||
@suites[klass.name] = klass.suite if Test::Unit::TestCase > klass | ||
end | ||
|
||
@pending = @suites.keys.sort_by do |name| | ||
[ | ||
0 - timings.fetch(test_name, name), # runtime, descending | ||
0 - @suites[name].size, # no. of tests, descending | ||
name | ||
] | ||
end | ||
end | ||
|
||
def next_suite | ||
@pending.shift | ||
end | ||
|
||
def suite_for(name) | ||
@suites[name] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
module RailsParallel | ||
module Forks | ||
def fork_and_run | ||
ActiveRecord::Base.connection.disconnect! if ActiveRecord::Base.connected? | ||
|
||
fork do | ||
begin | ||
yield | ||
Kernel.exit!(0) | ||
rescue Interrupt, SignalException | ||
Kernel.exit!(1) | ||
rescue Exception => e | ||
puts "Error: #{e}" | ||
puts(*e.backtrace.map {|t| "\t#{t}"}) | ||
before_exit | ||
Kernel.exit!(1) | ||
end | ||
end | ||
end | ||
|
||
def wait_for(pid, nonblock = false) | ||
pid = Process.waitpid(pid, nonblock ? Process::WNOHANG : 0) | ||
check_status($?) if pid | ||
pid | ||
end | ||
|
||
def wait_any(nonblock = false) | ||
wait_for(-1, nonblock) | ||
end | ||
|
||
def check_status(stat) | ||
raise "error: #{stat.inspect}" unless stat.success? | ||
end | ||
|
||
def before_exit | ||
# cleanup here (in children) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
require 'rubygems' | ||
require 'socket' | ||
|
||
class ObjectSocket | ||
BLOCK_SIZE = 4096 | ||
|
||
attr_reader :socket | ||
|
||
def self.pair | ||
Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0).map { |s| new(s) } | ||
end | ||
|
||
def initialize(socket) | ||
@socket = socket | ||
@buffer = '' | ||
end | ||
|
||
def nonblock=(val) | ||
@nonblock = val | ||
end | ||
|
||
def close | ||
@socket.close | ||
end | ||
|
||
def nonblocking(&block) | ||
with_nonblock(true, &block) | ||
end | ||
def blocking(&block) | ||
with_nonblock(false, &block) | ||
end | ||
|
||
def each_object(&block) | ||
first = true | ||
loop do | ||
process_buffer(&block) if first | ||
first = false | ||
|
||
@buffer += @nonblock ? @socket.read_nonblock(BLOCK_SIZE) : @socket.readpartial(BLOCK_SIZE) | ||
process_buffer(&block) | ||
end | ||
rescue Errno::EAGAIN | ||
# end of nonblocking data | ||
end | ||
|
||
def next_object | ||
each_object { |o| return o } | ||
nil # no pending data in nonblock mode | ||
end | ||
|
||
def <<(obj) | ||
data = Marshal.dump(obj) | ||
@socket.syswrite [data.size, data].pack('Na*') | ||
self # chainable | ||
end | ||
|
||
private | ||
|
||
def process_buffer | ||
while @buffer.size >= 4 | ||
size = 4 + @buffer.unpack('N').first | ||
break unless @buffer.size >= size | ||
|
||
packet = @buffer.slice!(0, size) | ||
yield Marshal.load(packet[4..-1]) | ||
end | ||
end | ||
|
||
def with_nonblock(value) | ||
old_value = @nonblock | ||
@nonblock = value | ||
return yield | ||
ensure | ||
@nonblock = old_value | ||
end | ||
end |
Oops, something went wrong.