├── .ruby-version ├── spec ├── lib │ └── rspec_preloader │ │ └── file_watcher_spec.rb ├── fake_project │ └── spec │ │ ├── spec_helper.rb │ │ └── some_class_spec.rb ├── spec_helper.rb ├── rspec_preloader │ └── file_watcher_spec.rb └── rspec_preloader_spec.rb ├── .gitignore ├── Gemfile ├── bin ├── rspec-preloader ├── rspec-preloader-server └── rspec-preloader-client ├── Rakefile ├── lib ├── rspec_preloader │ ├── server.rb │ ├── client.rb │ ├── command_line.rb │ ├── file_selector.rb │ └── spec_runner.rb └── rspec_preloader.rb ├── rspec_preloader.gemspec ├── LICENSE └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.1.1 2 | -------------------------------------------------------------------------------- /spec/lib/rspec_preloader/file_watcher_spec.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | rspec-preloader*.gem 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /spec/fake_project/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | puts "Loading spec helper" 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require_relative "../lib/rspec_preloader" 2 | -------------------------------------------------------------------------------- /bin/rspec-preloader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../lib/rspec_preloader" 4 | 5 | RspecPreloader.run(ARGV) 6 | -------------------------------------------------------------------------------- /bin/rspec-preloader-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../lib/rspec_preloader" 4 | 5 | RspecPreloader.run_server 6 | -------------------------------------------------------------------------------- /bin/rspec-preloader-client: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../lib/rspec_preloader" 4 | 5 | RspecPreloader.run_client(ARGV) 6 | -------------------------------------------------------------------------------- /spec/fake_project/spec/some_class_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "some class" do 4 | it "is a spec" do 5 | puts "Running specs" 6 | end 7 | end 8 | 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :build 2 | 3 | task :build do 4 | `gem build rspec_preloader.gemspec` 5 | end 6 | 7 | task :test do 8 | puts `bundle exec ruby spec/*_spec.rb` 9 | end 10 | -------------------------------------------------------------------------------- /spec/rspec_preloader/file_watcher_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "spec_helper" 2 | require "minitest/autorun" 3 | 4 | class TestFileWatcher < Minitest::Test 5 | 6 | def test_that_it_returns_changed_files 7 | assert_equal FileWatcher.changed_files, "app/some_class.rb" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rspec_preloader/server.rb: -------------------------------------------------------------------------------- 1 | require 'drb/drb' 2 | 3 | class RspecPreloader 4 | 5 | SERVER_URI = "druby://localhost:8787" 6 | 7 | class Server 8 | 9 | $SAFE = 1 # disable eval() and friends 10 | 11 | def self.run 12 | DRb.start_service(SERVER_URI, SpecRunner) 13 | DRb.thread.join 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rspec_preloader/client.rb: -------------------------------------------------------------------------------- 1 | require 'drb/drb' 2 | require_relative 'server' 3 | 4 | class RspecPreloader 5 | class Client 6 | 7 | def self.run(rspec_arguments) 8 | DRb.start_service 9 | 10 | rspec_runner = DRbObject.new_with_uri(SERVER_URI) 11 | rspec_runner.run_rspec(rspec_arguments, STDERR, STDOUT) 12 | end 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /rspec_preloader.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |gem| 2 | gem.name = 'rspec-preloader' 3 | gem.version = '1.1.0' 4 | gem.licenses = ['MIT'] 5 | gem.summary = "Start Rspec instantly" 6 | gem.description = "Life is too short to be waiting for your tests to load." 7 | gem.authors = ["Victor Mours"] 8 | gem.email = 'victor.mours@gmail.com' 9 | gem.files = Dir["lib/**/*.rb"] 10 | gem.executables = ["rspec-preloader", "rspec-preloader-server", "rspec-preloader-client"] 11 | gem.homepage = 'https://github.com/victormours/rspec-preloader' 12 | 13 | gem.add_runtime_dependency "rspec" 14 | 15 | gem.add_development_dependency "minitest" 16 | gem.add_development_dependency "pry" 17 | end 18 | -------------------------------------------------------------------------------- /lib/rspec_preloader.rb: -------------------------------------------------------------------------------- 1 | require_relative "rspec_preloader/command_line" 2 | require_relative "rspec_preloader/server" 3 | require_relative "rspec_preloader/client" 4 | 5 | class RspecPreloader 6 | 7 | def self.run(rspec_arguments = nil) 8 | setup 9 | CommandLine.run(rspec_arguments) 10 | end 11 | 12 | def self.run_server 13 | setup 14 | Server.run 15 | end 16 | 17 | def self.run_client(rspec_arguments) 18 | Client.run(rspec_arguments) 19 | end 20 | 21 | private 22 | 23 | def self.setup 24 | trap_int_signal 25 | load_spec_helper 26 | end 27 | 28 | def self.trap_int_signal 29 | trap("INT") do 30 | puts "Shutting down rspec-preloader" 31 | exit 32 | end 33 | end 34 | 35 | def self.load_spec_helper 36 | print "Loading spec_helper..." 37 | require "#{Dir.pwd}/spec/spec_helper" 38 | puts "done." 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /lib/rspec_preloader/command_line.rb: -------------------------------------------------------------------------------- 1 | require "readline" 2 | require_relative "spec_runner" 3 | 4 | class RspecPreloader 5 | class CommandLine 6 | def self.run(rspec_arguments = ['']) 7 | new(rspec_arguments).run 8 | end 9 | 10 | def initialize(rspec_arguments) 11 | @rspec_arguments = rspec_arguments 12 | end 13 | 14 | def run 15 | first_run 16 | command_line_loop 17 | end 18 | 19 | private 20 | 21 | def first_run 22 | return if @rspec_arguments == "" 23 | Readline::HISTORY << @rspec_arguments.join(" ") 24 | SpecRunner.run_rspec(@rspec_arguments) 25 | end 26 | 27 | def command_line_loop 28 | loop do 29 | rspec_arguments = Readline.readline("rspec > ", true) 30 | break if [nil, "exit"].include?(rspec_arguments) 31 | rspec_arguments_array = rspec_arguments.chomp.split(" ") 32 | SpecRunner.run_rspec(rspec_arguments_array) 33 | end 34 | end 35 | 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rspec_preloader/file_selector.rb: -------------------------------------------------------------------------------- 1 | class FileSelector 2 | def self.updated_source_files 3 | new.updated_source_files 4 | end 5 | 6 | def updated_source_files 7 | all_updated_files.select do |filename| 8 | filename.start_with?('app/') || filename.start_with?('lib/') 9 | end.select do |filename| 10 | filename.end_with?('.rb') 11 | end 12 | end 13 | 14 | private 15 | 16 | def all_updated_files 17 | file_statuses_and_names.select do |file_status_and_name| 18 | updated_files_statuses.include? file_status_and_name.first 19 | end 20 | .map(&:last) 21 | end 22 | 23 | def updated_files_statuses 24 | ['M', 'MM', 'A', '??'] 25 | end 26 | 27 | def file_statuses_and_names 28 | git_status_lines.map do |git_status_line| 29 | git_status_line.split(' ') 30 | end 31 | end 32 | 33 | def git_status_lines 34 | git_status.split("\n") 35 | end 36 | 37 | def git_status 38 | `git status --porcelain` 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rspec_preloader/spec_runner.rb: -------------------------------------------------------------------------------- 1 | require "rspec/core" 2 | require_relative "file_selector" 3 | 4 | class RspecPreloader 5 | class SpecRunner 6 | 7 | def self.run_rspec(rspec_arguments_array, std_err = STDERR, std_out = STDOUT) 8 | new(rspec_arguments_array, std_err, std_out).run_rspec 9 | end 10 | 11 | def initialize(rspec_arguments_array, std_err, std_out) 12 | @rspec_arguments = rspec_arguments_array 13 | @std_err = std_err 14 | @std_out = std_out 15 | end 16 | 17 | def run_rspec 18 | pid = fork do 19 | load_updated_files 20 | run_specs(@rspec_arguments) 21 | end 22 | Process.wait(pid) 23 | end 24 | 25 | private 26 | 27 | def load_updated_files 28 | FileSelector.updated_source_files.each do |file| 29 | load file 30 | end 31 | end 32 | 33 | def run_specs(arguments_array) 34 | RSpec::Core::Runner.run(arguments_array, @std_err, @std_out) 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Victor Mours 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/rspec_preloader_spec.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "minitest/spec" 3 | 4 | require 'tempfile' 5 | 6 | describe "rspec-preloader" do 7 | 8 | it "outputs the same thing as Rspec" do 9 | puts preloader_output 10 | puts rspec_output 11 | preloader_output.must_include(rspec_output) 12 | end 13 | 14 | def preloader_output 15 | preloader_output_file = Tempfile.new("prealoader") 16 | 17 | system [ 18 | "cd #{fake_project_path}", 19 | "echo exit | ruby #{preloader_bin_path} spec > #{preloader_output_file.path}" 20 | ].join(";") 21 | 22 | preloader_output_file.readlines 23 | end 24 | 25 | def rspec_output 26 | rspec_output_file = Tempfile.new("rspec") 27 | 28 | system [ 29 | "cd #{fake_project_path}", 30 | "rspec spec > #{rspec_output_file.path}" 31 | ].join(";") 32 | 33 | rspec_output_file.readlines 34 | end 35 | 36 | def cd_to_fake_project 37 | "cd #{fake_project_path}" 38 | end 39 | 40 | def fake_project_path 41 | File.expand_path("fake_project", __dir__) 42 | end 43 | 44 | def preloader_bin_path 45 | File.expand_path("../bin/rspec-preloader", __dir__) 46 | end 47 | end 48 | 49 | 50 | # def source_filename 51 | # File.expand_path("fake_project/app/some_class.rb", __dir__) 52 | # end 53 | # 54 | # def setup 55 | # File.open(source_filename, 'w') do |file| 56 | # file.puts "puts 'Loading source file'" 57 | # end 58 | # end 59 | # 60 | # def teardown 61 | # File.delete(source_filename) 62 | # end 63 | # 64 | # def preloader_output 65 | # `cd #{File.dirname(__FILE__)}/fake_project; ruby ../../bin/rspec-preloader.rb`.split("\n") 66 | # end 67 | # 68 | # def test_that_output_has_server_status 69 | # assert_equal preloader_output[0], "Hello" 70 | # end 71 | # 72 | # def test_spec_helper_gets_loaded 73 | # assert_equal "Loading spec helper", preloader_output[1] 74 | # end 75 | # 76 | # def test_changed_source_file_is_loaded 77 | # assert_equal "Loading source file", preloader_output[2] 78 | # end 79 | # 80 | # def test_specs_are_run 81 | # assert_equal "Running specs", preloader_output[3] 82 | # end 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rspec-preloader 2 | =============== 3 | 4 | Life is too short to be waiting for your tests to load. 5 | --- 6 | 7 | ![rspec-preloader](https://cloud.githubusercontent.com/assets/1840367/7346923/71b0b9ea-ece3-11e4-8423-be241b365d50.gif) 8 | 9 | 10 | rspec-preloader loads your `spec_helper.rb` so that your tests can be started almost instantly. No more waiting for your gems and your whole environment to load. 11 | 12 | Installing 13 | --- 14 | No surprises: 15 | ``` 16 | gem install rspec-preloader 17 | ``` 18 | 19 | Or in your Gemfile 20 | ``` 21 | gem 'rspec-preloader' 22 | ``` 23 | 24 | Command Line Usage 25 | --- 26 | 27 | ``` 28 | $rspec-preloader spec/models/some_cool_class_spec.rb 29 | ``` 30 | This will run `rspec spec/models/some_cool_class_spec.rb` and wait for additional input. 31 | 32 | Now write some code to make the test green, go back to your shell and press Up and Enter. 33 | 34 | This will instantly run `rspec spec/models/some_cool_class_spec.rb` again. 35 | 36 | Now refactor, and rerun as many times as you want. 37 | 38 | Once you're done, Control-D or `exit` out of the preloader. 39 | 40 | If you want to run rspec with any other arguments, you can give them to the preloader readline or when calling the command. 41 | 42 | Client/Server Usage 43 | --- 44 | 45 | ``` 46 | $ cd /my/cool/project 47 | $ rspec-preloader-server 48 | ``` 49 | And then run `rspec-preloader-client` just like you would usually run `rspec`. 50 | Restart your server each time you commit (see the How it works session to find out why). 51 | 52 | 53 | rspec-preloader will pass your input to rspec as is, so you can use any input rspec would accept, including specific line numbers or formatting options. 54 | Only files from `app/` and `lib/` will be reloaded. If you modify spec helpers or spec support files, you should probably restart the preloader. 55 | 56 | How it works 57 | --- 58 | This is what happens under the hood : 59 | - require `spec/spec_helper.rb`, and waits for input. 60 | - fork a process and run the tests in that process 61 | - wait for the next command 62 | - fork 63 | - find which ruby files in `app/` and `lib/` have been modified using git 64 | - load them 65 | - run rspec in the forked process with the given input 66 | - start over 67 | 68 | Issues 69 | --- 70 | Some frameworks like Grape don't like having client code reloaded a bunch or time. 71 | When working with these, your specs might be red and magically go back to green when you restart the preloader. 72 | Hopefully, I haven't yet faced the case where a spec is green with the preloader and red without it, but you never know. 73 | 74 | If you find any other issue, open it on Github, I'd be really happy to read about it and find a fix. 75 | 76 | Contributing 77 | --- 78 | Contributions welcome! Just fork it and send a pull request. 79 | 80 | This gem is under the MIT license by the way. 81 | 82 | --------------------------------------------------------------------------------