Permalink
Browse files

Merge pull request #64 from grosser/namespave

namespace everything
  • Loading branch information...
2 parents 4cf7b21 + 7e3acfb commit 095c4a6320ae805ca333dd37a6c25bea1b5c0c3e @jstorimer committed Aug 8, 2012
Showing with 374 additions and 360 deletions.
  1. +2 −360 bin/spin
  2. +268 −0 lib/spin.rb
  3. +76 −0 lib/spin/cli.rb
  4. +28 −0 lib/spin/hooks.rb
View
362 bin/spin
@@ -1,361 +1,3 @@
#!/usr/bin/env ruby
-# Spin will speed up your autotest(ish) workflow for Rails.
-
-# Spin preloads your Rails environment for testing, so you don't load the same code over and over and over... Spin works best with an autotest(ish) workflow.
-
-require 'socket'
-# This brings in `Dir.tmpdir`
-require 'tempfile'
-# This lets us hash the parameters we want to include in the filename
-# without having to worry about subdirectories, special chars, etc.
-require 'digest/md5'
-# So we can tell users how much time they're saving by preloading their
-# environment.
-require 'benchmark'
-require 'optparse'
-require 'pathname'
-
-SEPARATOR = '|'
-
-def usage
- <<-USAGE
-Usage: spin serve
- spin push <file> <file>...
-Spin preloads your Rails environment to speed up your autotest(ish) workflow.
- USAGE
-end
-
-def socket_file
- key = Digest::MD5.hexdigest [Dir.pwd, 'spin-gem'].join
- [Dir.tmpdir, key].join('/')
-end
-
-def determine_test_framework(force_rspec, force_testunit)
- if force_rspec
- :rspec
- elsif force_testunit
- :testunit
- elsif defined?(RSpec)
- :rspec
- else
- :testunit
- end
-end
-
-def disconnect(connection)
- connection.print "\0"
- connection.close
-end
-
-def rails_root(preload)
- path = Pathname.pwd
- until path.join(preload).file?
- return if path.root?
- path = path.parent
- end
- path
-end
-
-# ## spin serve
-def serve(force_rspec, force_testunit, time, push_results, preload)
- root_path = rails_root(preload) and Dir.chdir(root_path)
- file = socket_file
- Spin.parse_hook_file(root_path) if root_path
-
- # We delete the tmp file for the Unix socket if it already exists. The file
- # is scoped to the `pwd`, so if it already exists then it must be from an
- # old run of `spin serve` and can be cleaned up.
- File.delete(file) if File.exist?(file)
-
- # This socket is how we communicate with `spin push`.
- socket = UNIXServer.open(file)
-
- # Trap SIGINT (Ctrl-C) so that we exit cleanly.
- trap('SIGINT') {
- socket.close
- exit
- }
-
- ENV['RAILS_ENV'] = 'test' unless ENV['RAILS_ENV']
-
- test_framework = nil
-
- if root_path
- sec = Benchmark.realtime {
- # We require config/application because that file (typically) loads Rails
- # and any Bundler deps, as well as loading the initialization code for
- # the app, but it doesn't actually perform the initialization. That happens
- # in config/environment.
- #
- # In my experience that's the best we can do in terms of preloading. Rails
- # and the gem dependencies rarely change and so don't need to be reloaded.
- # But you can't initialize the application because any non-trivial app will
- # involve it's models/controllers, etc. in its initialization, which you
- # definitely don't want to preload.
- Spin.execute_hook(:before_preload)
- require File.expand_path preload.sub('.rb','')
- Spin.execute_hook(:after_preload)
-
- # Determine the test framework to use using the passed-in 'force' options
- # or else default to checking for defined constants.
- test_framework = determine_test_framework(force_rspec, force_testunit)
-
- # Preload RSpec to save some time on each test run
- begin
- require 'rspec/autorun'
-
- # Tell RSpec it's running with a tty to allow colored output
- if RSpec.respond_to?(:configure)
- RSpec.configure do |c|
- c.tty = true if c.respond_to?(:tty=)
- end
- end
- rescue LoadError
- end if test_framework == :rspec
- }
- # This is the amount of time that you'll save on each subsequent test run.
- puts "Preloaded Rails env in #{sec}s..."
- else
- warn "Could not find #{preload}. Are you running this from the root of a Rails project?"
- end
-
- puts "Pushing test results back to push processes" if push_results
-
- loop do
-
- # If we're not going to push the results,
- # Trap SIGQUIT (Ctrl+\) and re-run the last files that were
- # pushed.
- if !push_results
- trap('QUIT') do
- fork_and_run(@last_files_ran, push_results, test_framework, nil)
- # See WAIT below
- Process.wait
- end
- end
-
- # Since `spin push` reconnects each time it has new files for us we just
- # need to accept(2) connections from it.
- conn = socket.accept
- # This should be a list of relative paths to files.
- files = conn.gets.chomp
- files = files.split(SEPARATOR)
-
- # If spin is started with the time flag we will track total execution so
- # you can easily compare it with time rspec spec for example
- start = Time.now if time
-
- # If we're not sending results back to the push process, we can disconnect
- # it immediately.
- disconnect(conn) unless push_results
-
- fork_and_run(files, push_results, test_framework, conn)
-
- # WAIT: We don't want the parent process handling multiple test runs at the same
- # time because then we'd need to deal with multiple test databases, and
- # that destroys the idea of being simple to use. So we wait(2) until the
- # child process has finished running the test.
- Process.wait
-
- # If we are tracking time we will output it here after everything has
- # finished running
- puts "Total execution time was #{Time.now - start} seconds" if start
-
- # Tests have now run. If we were pushing results to a push process, we can
- # now disconnect it.
- begin
- disconnect(conn) if push_results
- rescue Errno::EPIPE
- # Don't abort if the client already disconnected
- end
- end
-ensure
- File.delete(file) if file && File.exist?(file)
-end
-
-def fork_and_run(files, push_results, test_framework, conn)
- Spin.execute_hook(:before_fork)
- # We fork(2) before loading the file so that our pristine preloaded
- # environment is untouched. The child process will load whatever code it
- # needs to, then it exits and we're back to the baseline preloaded app.
- fork do
- # To push the test results to the push process instead of having them
- # displayed by the server, we reopen $stdout/$stderr to the open
- # connection.
- tty = files.delete "tty?"
- if push_results
- $stdout.reopen(conn)
- if tty
- def $stdout.tty?
- true
- end
- end
- $stderr.reopen(conn)
- end
-
- Spin.execute_hook(:after_fork)
-
- puts
- puts "Loading #{files.inspect}"
-
- # Unfortunately rspec's interface isn't as simple as just requiring the
- # test file that you want to run (suddenly test/unit seems like the less
- # crazy one!).
- if test_framework == :rspec
- # We pretend the filepath came in as an argument and duplicate the
- # behaviour of the `rspec` binary.
- ARGV.push files
- else
- # We require the full path of the file here in the child process.
- files.each { |f| require File.expand_path f }
- end
-
- end
- @last_files_ran = files
-end
-
-# ## spin push
-def push(preload)
- # The filenames that we will spin up to `spin serve` are passed in as
- # arguments.
- files_to_load = ARGV
-
- # We reject anything in ARGV that isn't a file that exists. This takes
- # care of scripts that specify files like `spin push -r file.rb`. The `-r`
- # bit will just be ignored.
- #
- # We build a string like `file1.rb|file2.rb` and pass it up to the server.
- files_to_load = files_to_load.map do |file|
- args = file.split(':')
-
- file_name = args.first.to_s
- line_number = args.last.to_i
-
- # If the file exists then we can push it up just like it is
- file_name = if File.exist?(file_name)
- file_name
- # kicker-2.5.0 now gives us file names without extensions, so we have to try adding it
- elsif File.extname(file_name).empty?
- full_file_name = [file_name, 'rb'].join('.')
- full_file_name if File.exist?(full_file_name)
- end
-
- if line_number > 0
- abort "You specified a line number. Only one file can be pushed in this case." if files_to_load.length > 1
-
- "#{file_name}:#{line_number}"
- else
- file_name
- end
- end.compact.uniq
-
- if root_path = rails_root(preload)
- files_to_load.map! do |file|
- Pathname.new(file).expand_path.relative_path_from(root_path).to_s
- end
- Dir.chdir root_path
- end
-
- files_to_load << "tty?" if $stdout.tty?
- f = files_to_load.join(SEPARATOR)
-
- abort if f.empty?
- puts "Spinning up #{f}"
-
- # This is the other end of the socket that `spin serve` opens. At this point
- # `spin serve` will accept(2) our connection.
- socket = UNIXSocket.open(socket_file)
- # We put the filenames on the socket for the server to read and then load.
- socket.puts f
-
- while line = socket.readpartial(100)
- break if line[-1,1] == "\0"
- print line
- end
-rescue Errno::ECONNREFUSED, Errno::ENOENT
- abort "Connection was refused. Have you started up `spin serve` yet?"
-end
-
-module Spin
- HOOKS = [:before_fork, :after_fork, :before_preload, :after_preload]
-
- def self.hook(name, &block)
- raise unless HOOKS.include?(name)
- _hooks(name) << block
- end
-
- def self.execute_hook(name)
- raise unless HOOKS.include?(name)
- _hooks(name).each(&:call)
- end
-
- def self.parse_hook_file(root)
- file = root.join(".spin.rb")
- load(file) if File.exist?(file)
- end
-
- private
-
- def self._hooks(name)
- @hooks ||= {}
- @hooks[name] ||= []
- @hooks[name]
- end
-end
-
-force_rspec = false
-force_testunit = false
-time = false
-push_results = false
-preload = "config/application.rb"
-options = OptionParser.new do |opts|
- opts.banner = usage
- opts.separator ""
- opts.separator "Server Options:"
-
- opts.on("-I", "--load-path=DIR#{File::PATH_SEPARATOR}DIR", "Appends directory to $LOAD_PATH") do |dirs|
- $LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
- end
-
- opts.on('--rspec', 'Force the selected test framework to RSpec') do |v|
- force_rspec = v
- end
-
- opts.on('--test-unit', 'Force the selected test framework to Test::Unit') do |v|
- force_testunit = v
- end
-
- opts.on('-t', '--time', 'See total execution time for each test run') do |v|
- time = true
- end
-
- opts.on('--push-results', 'Push test results to the push process') do |v|
- push_results = v
- end
-
- opts.on('--preload FILE', "Preload this file instead of #{preload}") do |v|
- preload = v
- end
-
- opts.separator "General Options:"
- opts.on('-e', 'Stub to keep kicker happy')
- opts.on('-v', '--version', 'Show Version') do
- require 'spin/version'
- puts Spin::VERSION; exit
- end
- opts.on('-h', '--help') do
- $stderr.puts opts
- exit
- end
-end
-options.parse!
-
-subcommand = ARGV.shift
-case subcommand
-when 'serve' then serve(force_rspec, force_testunit, time, push_results, preload)
-when 'push' then push(preload)
-else
- $stderr.puts options
- exit 1
-end
-
+require 'spin/cli'
+Spin::CLI.run(ARGV)
Oops, something went wrong.

0 comments on commit 095c4a6

Please sign in to comment.