spinoff 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +89 -0
- data/Rakefile +2 -0
- data/bin/spinoff-client +8 -0
- data/bin/spinoff-server +36 -0
- data/lib/spinoff/client.rb +22 -0
- data/lib/spinoff/server.rb +14 -0
- data/lib/spinoff/server/fork.rb +39 -0
- data/lib/spinoff/server/generic.rb +49 -0
- data/lib/spinoff/server/jruby.rb +44 -0
- data/lib/spinoff/socket_path.rb +8 -0
- data/lib/spinoff/test_runner.rb +16 -0
- data/lib/spinoff/test_runner/rspec.rb +17 -0
- data/lib/spinoff/test_runner/test_unit.rb +9 -0
- data/lib/spinoff/version.rb +3 -0
- data/spinoff.gemspec +17 -0
- metadata +64 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Spinoff
|
2
|
+
|
3
|
+
Spinoff will help you to speed up your Ruby test workflow.
|
4
|
+
|
5
|
+
It does that by preloading your environment and then using functions like
|
6
|
+
`fork` for each run of your test suite to avoid loading framework code
|
7
|
+
like Rails over and over again. (it supports JRuby as well)
|
8
|
+
|
9
|
+
# Credits
|
10
|
+
|
11
|
+
Lots of code has been taken from the [Spin](https://github.com/jstorimer/spin)
|
12
|
+
project by Jesse Storimer. Thanks for sharing, Jesse!
|
13
|
+
|
14
|
+
# How does it work
|
15
|
+
|
16
|
+
Spinoff operates with the assumption that most of the dependencies of your
|
17
|
+
application do not change. If you are working on a Rails project, you
|
18
|
+
probably do not have to reload all Rails classes for each test run.
|
19
|
+
|
20
|
+
The `spinoff-server` command starts a server process that will listen for
|
21
|
+
a list of test files on a unix domain socket. It will also load an init file
|
22
|
+
which contains all code that should be preloaded.
|
23
|
+
(like `require 'config/application'` for Rails)
|
24
|
+
|
25
|
+
The `spinoff-client` command will be called with a list of test files that
|
26
|
+
should be executed. It sends that file list to the server process via the
|
27
|
+
unix domain socket.
|
28
|
+
|
29
|
+
Once the server process receives a list of files, it will fork the Ruby process
|
30
|
+
and execute the files with the selected test runner. All code that changes
|
31
|
+
frequently will only be loaded in the forked process.
|
32
|
+
|
33
|
+
This allows Spinoff to speed up our test suite without any knowledge of the
|
34
|
+
frameworks and libraries we use to build our app.
|
35
|
+
|
36
|
+
# How is it different from Spin?
|
37
|
+
|
38
|
+
Spinoff is based on [Spin](https://github.com/jstorimer/spin) but differs on
|
39
|
+
the following points.
|
40
|
+
|
41
|
+
1. It is framework agnostic and thus needs an init file to preload code.
|
42
|
+
Spin does only support Rails at this time of writing.
|
43
|
+
|
44
|
+
2. It works with [JRuby](http://www.jruby.org/)!
|
45
|
+
|
46
|
+
# Usage
|
47
|
+
|
48
|
+
Spinoff needs an init file to preload code. Please make sure you only load
|
49
|
+
libraries that do not change between your test runs. If you change anything
|
50
|
+
you preload in the init file, you have to restart the Spinoff server.
|
51
|
+
|
52
|
+
Example:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'config/application'
|
56
|
+
require 'rspec'
|
57
|
+
```
|
58
|
+
|
59
|
+
Starting the server:
|
60
|
+
|
61
|
+
$ spinoff-server --rspec config/spinoff.rb
|
62
|
+
|
63
|
+
Please use `spinoff-server --help` to show all available options.
|
64
|
+
|
65
|
+
Sending a list of test files to the server:
|
66
|
+
|
67
|
+
$ spinoff-client spec/test1_spec.rb spec/foo/bar_spec.rb
|
68
|
+
|
69
|
+
Spinoff should work well with autotest(ish) tools. Just start the server and
|
70
|
+
execute the spinoff client on file changes.
|
71
|
+
|
72
|
+
# JRuby Support
|
73
|
+
|
74
|
+
Since JRuby does not support the fork system call, Spinoff is using a
|
75
|
+
new `ScriptingContainer` for each test run. That also means we cannot really
|
76
|
+
preload any code. But it saves us from starting a new JVM for each test run.
|
77
|
+
|
78
|
+
# Contributing
|
79
|
+
|
80
|
+
I'm happy about any kind of feedback and/or contribution.
|
81
|
+
|
82
|
+
# Caveats
|
83
|
+
|
84
|
+
* New and not well tested.
|
85
|
+
* No test suite yet.
|
86
|
+
|
87
|
+
# Author
|
88
|
+
|
89
|
+
[Bernd Ahlers](https://github.com/bernd)
|
data/Rakefile
ADDED
data/bin/spinoff-client
ADDED
data/bin/spinoff-server
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'optparse'
|
5
|
+
require 'spinoff/server'
|
6
|
+
|
7
|
+
config = {}
|
8
|
+
|
9
|
+
parser = OptionParser.new do |o|
|
10
|
+
o.banner = 'Usage: spinoff-server [options] <init file>'
|
11
|
+
|
12
|
+
o.on('--rspec', 'Use rspec framework (default)') do |v|
|
13
|
+
config[:test_framework] = :rspec
|
14
|
+
end
|
15
|
+
|
16
|
+
o.on('--test-unit', 'Use test/unit framework') do |v|
|
17
|
+
config[:test_framework] = :testunit
|
18
|
+
end
|
19
|
+
|
20
|
+
o.on('-h', '--help') do
|
21
|
+
STDERR.puts o
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
parser.parse!
|
26
|
+
|
27
|
+
unless config[:test_framework]
|
28
|
+
STDERR.puts "Please select a test framework! (see --help)"
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
config[:init_script] = ARGV.shift unless ARGV.empty?
|
33
|
+
|
34
|
+
Spinoff::Server.start(config)
|
35
|
+
|
36
|
+
exit 0
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'spinoff/socket_path'
|
3
|
+
|
4
|
+
module Spinoff
|
5
|
+
module Client
|
6
|
+
def self.start(argv)
|
7
|
+
files = argv.select {|f| File.exist?(f) }.uniq.join(File::PATH_SEPARATOR)
|
8
|
+
|
9
|
+
return if files.empty?
|
10
|
+
|
11
|
+
socket = UNIXSocket.open(Spinoff.socket_path)
|
12
|
+
socket.puts files
|
13
|
+
|
14
|
+
while line = socket.gets
|
15
|
+
break if line == "\0"
|
16
|
+
print line
|
17
|
+
end
|
18
|
+
rescue Errno::ECONNREFUSED
|
19
|
+
abort "Connection to spinoff server was refused."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Spinoff
|
2
|
+
module Server
|
3
|
+
def self.start(config)
|
4
|
+
case RUBY_PLATFORM
|
5
|
+
when /java/i
|
6
|
+
require 'spinoff/server/jruby'
|
7
|
+
Spinoff::Server::JRuby.start(config)
|
8
|
+
else
|
9
|
+
require 'spinoff/server/fork'
|
10
|
+
Spinoff::Server::Fork.start(config)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spinoff/server/generic'
|
2
|
+
require 'spinoff/test_runner'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
module Spinoff
|
6
|
+
module Server
|
7
|
+
class Fork < Spinoff::Server::Generic
|
8
|
+
def start
|
9
|
+
test_runner = Spinoff::TestRunner.init(config[:test_framework])
|
10
|
+
load_init_script(init_script)
|
11
|
+
|
12
|
+
accept_loop do |files|
|
13
|
+
start = Time.now
|
14
|
+
|
15
|
+
fork do
|
16
|
+
STDERR.puts "Loading #{files.inspect}"
|
17
|
+
test_runner << files
|
18
|
+
end
|
19
|
+
|
20
|
+
Process.wait
|
21
|
+
|
22
|
+
STDERR.puts "Execution time: %.4fs" % (Time.now - start)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def load_init_script(file)
|
28
|
+
if File.exist?(file)
|
29
|
+
sec = Benchmark.realtime do
|
30
|
+
require File.expand_path(file)
|
31
|
+
end
|
32
|
+
STDERR.puts "Loaded init script in %.4fs (%s)" % [sec, file]
|
33
|
+
else
|
34
|
+
STDERR.puts "WARNING: Init script '#{file}' does not exist!"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'spinoff/socket_path'
|
3
|
+
|
4
|
+
module Spinoff
|
5
|
+
module Server
|
6
|
+
class Generic
|
7
|
+
def self.start(config)
|
8
|
+
new(config).start
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :config, :socket
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
@config = config
|
15
|
+
@socket = create_socket
|
16
|
+
end
|
17
|
+
|
18
|
+
def init_script
|
19
|
+
File.expand_path(@config.fetch(:init_script, File.join(Dir.pwd, 'spinoff.rb')))
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
raise "#{self.class}#start not implemented."
|
24
|
+
end
|
25
|
+
|
26
|
+
def accept_loop
|
27
|
+
loop do
|
28
|
+
connection = socket.accept
|
29
|
+
files = connection.gets.chomp.split(File::PATH_SEPARATOR)
|
30
|
+
|
31
|
+
disconnect_client(connection)
|
32
|
+
yield(files)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def disconnect_client(client)
|
37
|
+
client.print("\0")
|
38
|
+
client.close
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def create_socket
|
43
|
+
path = Spinoff.socket_path
|
44
|
+
File.delete(path) if File.exist?(path)
|
45
|
+
UNIXServer.open(path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'spinoff/server/generic'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
module Spinoff
|
6
|
+
module Server
|
7
|
+
class JRuby < Spinoff::Server::Generic
|
8
|
+
import 'org.jruby.embed.PathType'
|
9
|
+
import 'org.jruby.embed.LocalContextScope'
|
10
|
+
import 'org.jruby.embed.ScriptingContainer'
|
11
|
+
|
12
|
+
def start
|
13
|
+
accept_loop do |files|
|
14
|
+
start = Time.now
|
15
|
+
|
16
|
+
STDERR.puts "Creating JRuby scripting container"
|
17
|
+
context = ScriptingContainer.new(LocalContextScope::SINGLETHREAD)
|
18
|
+
|
19
|
+
if File.exist?(init_script)
|
20
|
+
sec = Benchmark.realtime do
|
21
|
+
context.run_scriptlet(PathType::ABSOLUTE, init_script)
|
22
|
+
end
|
23
|
+
STDERR.puts "Loaded init script in %.4fs (%s)" % [sec, init_script]
|
24
|
+
else
|
25
|
+
STDERR.puts "WARNING: Init script '#{init_script}' does not exist!"
|
26
|
+
end
|
27
|
+
|
28
|
+
STDERR.puts "Loading #{files.inspect}"
|
29
|
+
context.put('framework', config[:test_framework])
|
30
|
+
context.put('files', files)
|
31
|
+
|
32
|
+
context.run_scriptlet <<-__RUBY
|
33
|
+
require 'spinoff/test_runner'
|
34
|
+
runner = Spinoff::TestRunner.init(framework)
|
35
|
+
runner << files
|
36
|
+
__RUBY
|
37
|
+
context.terminate
|
38
|
+
|
39
|
+
STDERR.puts "Execution time: %.4fs" % (Time.now - start)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Spinoff
|
2
|
+
module TestRunner
|
3
|
+
def self.init(framework)
|
4
|
+
STDERR.puts "Test framework: #{framework}"
|
5
|
+
|
6
|
+
case framework
|
7
|
+
when :rspec
|
8
|
+
require 'spinoff/test_runner/rspec'
|
9
|
+
Spinoff::TestRunner::RSpec.new
|
10
|
+
when :testunit
|
11
|
+
require 'spinoff/test_runner/test_unit'
|
12
|
+
Spinoff::TestRunner::TestUnit.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rspec/core'
|
2
|
+
|
3
|
+
module Spinoff
|
4
|
+
module TestRunner
|
5
|
+
class RSpec
|
6
|
+
def <<(files)
|
7
|
+
# Overwrite the trap_interrupt method to avoid weird behaviour
|
8
|
+
# on SIGINT.
|
9
|
+
::RSpec::Core::Runner.class_eval do
|
10
|
+
def self.trap_interrupt; end
|
11
|
+
end
|
12
|
+
|
13
|
+
::RSpec::Core::Runner.run(files)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spinoff.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/spinoff/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Bernd Ahlers"]
|
6
|
+
gem.email = ["bernd@tuneafish.de"]
|
7
|
+
gem.description = %q{Environment preloader}
|
8
|
+
gem.summary = %q{Spinoff preloads your Ruby environment based on an initialization file.}
|
9
|
+
gem.homepage = "https://github.com/bernd/spinoff"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "spinoff"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Spinoff::VERSION
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spinoff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bernd Ahlers
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-26 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Environment preloader
|
15
|
+
email:
|
16
|
+
- bernd@tuneafish.de
|
17
|
+
executables:
|
18
|
+
- spinoff-client
|
19
|
+
- spinoff-server
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- .gitignore
|
24
|
+
- Gemfile
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- bin/spinoff-client
|
28
|
+
- bin/spinoff-server
|
29
|
+
- lib/spinoff/client.rb
|
30
|
+
- lib/spinoff/server.rb
|
31
|
+
- lib/spinoff/server/fork.rb
|
32
|
+
- lib/spinoff/server/generic.rb
|
33
|
+
- lib/spinoff/server/jruby.rb
|
34
|
+
- lib/spinoff/socket_path.rb
|
35
|
+
- lib/spinoff/test_runner.rb
|
36
|
+
- lib/spinoff/test_runner/rspec.rb
|
37
|
+
- lib/spinoff/test_runner/test_unit.rb
|
38
|
+
- lib/spinoff/version.rb
|
39
|
+
- spinoff.gemspec
|
40
|
+
homepage: https://github.com/bernd/spinoff
|
41
|
+
licenses: []
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.8.11
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Spinoff preloads your Ruby environment based on an initialization file.
|
64
|
+
test_files: []
|