pax_global_header00006660000000000000000000000064137210512750014515gustar00rootroot0000000000000052 comment=a85d32ef3726931b2b81abbb10a6e1c2964e7e32 spring-2.1.1/000077500000000000000000000000001372105127500130205ustar00rootroot00000000000000spring-2.1.1/.gitignore000066400000000000000000000002571372105127500150140ustar00rootroot00000000000000*.gem *.rbc .bundle .config .yardoc .rvmrc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp .ruby-version spring-2.1.1/.travis.yml000066400000000000000000000011331372105127500151270ustar00rootroot00000000000000language: ruby cache: bundler rvm: - 2.4.6 - 2.5.5 - 2.6.3 - ruby-head env: - RAILS_VERSION="~> 4.2.0" - RAILS_VERSION="~> 5.0.0" - RAILS_VERSION="~> 5.1.0" - RAILS_VERSION="~> 5.2.0" - RAILS_VERSION="~> 6.0.0.rc1" matrix: exclude: - rvm: 2.4.6 env: RAILS_VERSION="~> 6.0.0.rc1" - rvm: 2.6.3 env: RAILS_VERSION="~> 4.2.0" allow_failures: - rvm: ruby-head fast_finish: true before_install: - gem update --system 3.0.3 - gem uninstall bundler && gem install bundler -v 1.17.3 before_script: - travis_retry gem install rails --version "$RAILS_VERSION" spring-2.1.1/CHANGELOG.md000066400000000000000000000325221372105127500146350ustar00rootroot00000000000000## Next Release ## 2.1.1 * Avoid -I rubylibdir with default-gem bundler * Start server process in directory where command was called ## 2.1.0 * Add explicit support for Rails 6 (no changes were needed) * Drop support to Ruby 1.9, 2.0, 2.1, and 2.3 * Fix binstubs not being replaced when their quoting style was changed (#534) * Preserve comments right after the shebang line which might include magic comments such as `frozen_string_literal: true` * Fix binstub failures when Bundler's `BUNDLE_APP_CONFIG` environment variable is present (#545) * Properly suspend and resume on ctrl-z TSTP and CONT (#361) * Added support for `gems.rb` with Gemfile file name detection using Bundler method (#524) *MichaƂ Zalewski*, *JuPlutonic* ## 2.0.2 * Fix reloading when a watched directory contains a dangling symlink (#522) * Watcher logs its activity to the Spring log (#522) * Polling watcher stops polling after changes have been detected (#523) ## 2.0.1 * Fix bug which could cause Spring to hang when `Thread.abort_on_exception` is `true` (#497) ## 2.0.0 * Drop Rails 4.0 and 4.1 support * Add explicit support for Rails 5 (no changes were needed) * Use Bundler::LockfileParser to parse lockfile instead of regex (#492) ## 1.7.2 * Use `Spring.failsafe_thread` to prevent threads from aborting process due to `Thread.abort_on_exception` when set to `true` ## 1.7.1 * Specify absolute path to spring binfile when starting the server (#478) * Time out after 10 seconds if starting the spring server doesn't work (maybe related to #480, #479) * Prevent infinite boot loop when trying to restart the spring server due to client/server version mismatch (related to #479) ## 1.7.0 * Auto-restart server when server and client versions do not match * Add `spring server` command to explicitly start a Spring server process in the foreground, which logging to stdout. This will be useful to those who want to run spring more explicitly, but the real impetus was to enable running a spring server inside a Docker container. * Numerous other tweaks to better support running Spring inside containers (see https://github.com/jonleighton/spring-docker-example) ## 1.6.4 * Fix incompatibility with RubyGems 2.6.0. ## 1.6.3 * Fix problem with using Bundler 1.11 with a custom `BUNDLE_PATH` (#456) ## 1.6.2 * Fix problems with the implementation of the new "Running via Spring preloader" message (see #456, #457) * Print "Running via Spring preloader" message to stderr, not stdout ## 1.6.1 * support replaced backtraces / backtraces with only line and number ## 1.6.0 * show when spring is used automatically to remind people why things might fail, disable with `Spring.quiet = true` ## 1.5.0 * Make the temporary directory path used by spring contain the UID of the process so that spring can work on machines where multiple users share a single $TMPDIR. ## 1.4.3 * Support new binstub format and --remove option ## 1.4.2 * Don't supress non-spring load errors in binstub ## 1.4.1 * Enable terminal resize detection in rails console. ## 1.4.0 * Add support for client side hooks. `config/spring_client.rb` is loaded before bundler and before a server process is started, it can be used to add new top-level commands. * Do not boot up the server when using -h / --help ## 1.3.6 * Ensure the spawned server is loaded from the same version of the Spring gem as the client. Issue #295. ## 1.3.5 * Fix `rails test` command to run in test environment #403 - @eileencodes ## 1.3.4 * Add `rails test` command. ## 1.3.3 * Fix yet another problem with loading spring which seems to affect some/all rbenv users. Issue #390. ## 1.3.2 * Fix another problem with gems bundled from git repositories. This affected chruby and RVM users, and possibly others. See #383. ## 1.3.1 * Fix a problem with gems bundled from a git repository, where the `bin/spring` was generated before 1.3.0. ## 1.3.0 * Automatically restart spring after new commands are added. This means that you can add spring-commands-rspec to your Gemfile and then immediately start using it, without having to run `spring stop`. (Spring will effectively run `spring stop` for you.) * Make app reloading work in apps which spew out lots of output on startup (previously a buffer would fill up and cause the process to hang). Issue #332. * Make sure running `bin/spring` does not add an empty string to `Gem.path`. Issues #297, #310. * Fixed problem with `$0` including the command line args, which could confuse commands which try to parse `$0`. This caused the spring-commands-rspec to not work properly in some cases. Issue #369. * Add OpenBSD compatibility for `spring status`. Issue #299. * Rails 3.2 no longer officially supported (but it may continue to work) ## 1.2.0 * Accept -e and --environment options for `rails console`. * Watch `config/secrets.yml` by default. #289 - @morgoth * Change monkey-patched `Kernel.raise` from public to private (to match default Ruby behavior) #351 - @mattbrictson * Let application_id also respect RUBY_VERSION for the use case of switching between Ruby versions for a given Rails app - @methodmissing * Extract the 'listen' watcher to a separate `spring-watcher-listen` gem. This allows it to be developed/maintained separately. ## 1.1.3 * The `rails runner` command no longer passes environment switches to files which it runs. Issue #272. * Various issues solved to do with termination / processes hanging around longer than they should. Issue #290. ## 1.1.2 * Detect old binstubs generated with Spring 1.0 and exit with an error. This prevents a situation where you can get stuck in an infinite loop of spring invocations. * Avoid `warning: already initialized constant APP_PATH` when running rails commands that do not use spring (e.g. `bin/rails server` would emit this when you ^C to exit) * Fix `reload!` in rails console * Don't connect/disconnect the database if there are no connections configured. Issue #256. ## 1.1.1 * Fix `$0` so that it is no longer prefixed with "spring ", as doing this cause issues with rspec when running just `rspec` with no arguments. * Ensure we're always connected to a tty when preloading the application in the background, in order to avoid loading issues with readline + libedit which affected pry-rails. ## 1.1.0 * A `bin/spring` binstub is now generated. This allows us to load spring correctly if you have it installed locally with a `BUNDLE_PATH`, so it's no longer necessary to install spring system-wide. We also activate the correct version from your Gemfile.lock. Note that you still can't have spring in your Gemfile as a git repository or local path; it must be a proper gem. * Various changes to how springified binstubs are implemented. Existing binstubs will continue to work, but it's recommended to run `spring binstub` again to upgrade them to the new format. * `spring binstub --remove` option added for removing spring from binstubs. This won't work unless you have upgraded your binstubs to the new format. * `config/database.yml` is watched * Better application restarts - if you introduce an error, for example by editing `config/application.rb`, spring will now continue to watch your files and will immediately try to restart the application when you edit `config/application.rb` again (hopefully to correct the error). This means that by the time you come to run a command the application may well already be running. * Gemfile changes are now gracefully handled. Previously they would cause spring to simply quit, meaning that you'd incur the full startup penalty on the next run. Now spring doesn't quit, and will try to load up your new bundle in the background. * Fix support for using spring with Rails engines/plugins ## 1.0.0 * Enterprise ready secret sauce added ## 0.9.2 * Bugfix: environment variables set by bundler (`BUNDLE_GEMFILE`, `RUBYOPT`, etc...) were being removed from the environment. * Ensure we only run the code reloader when files have actually changed. This issue became more prominent with Rails 4, since Rails 4 will now reload routes whenever the code is reloaded (see https://github.com/rails/rails/commit/b9b06daa915fdc4d11e8cfe11a7175e5cd8f104f). * Allow spring to be used in a descendant directory of the application root * Use the system tmpdir for our temporary files. Previously we used `APP_ROOT/tmp/spring`, which caused problems on filesystems which did not support sockets, and also caused problems if `APP_ROOT` was sufficiently deep in the filesystem to exhaust the operating system's socket name limit. Hence we had a `SPRING_TMP_PATH` environment variable for configuration. We now use `/tmp/spring/[md5(APP_ROOT)]` for the socket and `/tmp/spring/[md5(APP_ROOT)].pid` for the pid file. Thanks @Kriechi for the suggestion. Setting `SPRING_TMP_PATH` no longer has any effect. ## 0.9.1 * Environment variables which were created during application startup are no longer overwritten. * Support for generating multiple binstubs at once. Use --all to generate all, otherwise you can pass multiple command names to the binstub command. * The `testunit` command has been extracted to the `spring-commands-testunit` gem, because it's not necessary in Rails 4, where you can just run `rake test path/to/test`. * The `~/.spring.rb` config file is loaded before bundler, so it's a good place to require extra commands which you want to use in all projects, without having to add those commands to the Gemfile of each individual project. * Any gems in the bundle with names which start with "spring-commands-" are now autoloaded. This makes it less faffy to add additional commands. ## 0.9.0 * Display spring version in the help message * Remove workaround for Rubygems performance issue. This issue is solved with Rubygems 2.1, so we no longer need to generate a "spring" binstub file. We warn users if they are not taking advantage of the Rubygems perf fix (e.g. if they are not on 2.1, or haven't run `gem pristine --all`). To upgrade, delete your `bin/spring` and re-run `spring binstub` for each of your binstubs. * Binstubs now fall back to non-spring execution of a command if the spring gem is not present. This might be useful for production environments. * The ENV will be replaced on each run to match the ENV which exists when the spring command is actually run (rather than the ENV which exists when spring first starts). * Specifying the rails env after the rake command (e.g. `rake RAILS_ENV=test db:migrate`) now works as expected. * Provide an explicit way to set the environment to use when running `rake` on its own. * The `rspec` and `cucumber` commands are no longer shipped by default. They've been moved to the `spring-commands-rspec` and `spring-commands-cucumber` gems. ## 0.0.11 * Added the `rails destroy` command. * Global config file in `~/.spring.rb` * Added logging for debugging. Specify a log file with the `SPRING_LOG` environment variable. * Fix hang on "Run `bundle install` to install missing gems" * Added hack to make backtraces generated when running a command quieter (by stripping out all of the lines relating to spring) * Rails 4 is officially supported ## 0.0.10 * Added `Spring.watch_method=` configuration option to switch between polling and the `listen` gem. Previously, we used the `listen` gem if it was available, but this makes the option explicit. Set `Spring.watch_method = :listen` to use the listen gem. * Fallback when Process.fork is not available. In such cases, the user will not receive the speedup that Spring provides, but won't receive an error either. * Don't preload `test_helper` or `spec_helper` by default. This was causing people subtle problems (for example see #113) and is perhaps surprising behaviour. It may be desirable but it depends on the application, therefore we suggest it to people in the README but no longer do it by default. * Don't stay connected to database in the application processes. There's no need to keep a connection open. * Avoid using the database in the application processes. Previously, reloading the autoloaded constants would inadvertantly cause a connection to the database, which would then prevent tasks like db:create from running (because at that point the database doesn't exist) * Removed ability to specify list of files for a command to preload. We weren't using this any more internally, and this is easy to do by placing requires in suitable locations in the Rails boot process (which is not explained in the README). * Seed the random number generator on each run. ## 0.0.9 * Added `Spring::Commands::Rake.environment_matchers` for matching rake tasks to specific environments. * Kill the spring server when the `Gemfile` or `Gemfile.lock` is changed. This forces a new server to boot up on the next run, which ensures that you get the correct gems (or the correct error message from bundler if you have forgotten to `bundle install`.) * Fixed error when `Spring.watch` is used in `config/spring.rb` ## 0.0.8 * Renamed `spring test` to `spring testunit`. * Implemented `spring rails` to replace `spring [console|runner|generate]`. * `config/spring.rb` is only loaded in the server process, so you can require stuff from other gems there without performance implications. * File watcher no longer pays attention to files outside of your application root directory. * You can use the `listen` gem for less CPU intensive file watching. See README. spring-2.1.1/CONTRIBUTING.md000066400000000000000000000032641372105127500152560ustar00rootroot00000000000000# Don't use the issue tracker to ask questions Please use Stack Overflow or similar. If you subsequently feel that the documentation is inadequate then please submit a pull request to fix it. # Contributing guide ## Getting set up Check out the code and run `bundle install` as usual. ## Running tests Running `rake` will run all tests. There are both unit tests and acceptance tests. You can run them individually with `rake test:unit` or `rake test:acceptance`. If one doesn't already exist, the acceptance tests will generate a dummy Rails app in `test/apps/`. On each test run, the dummy app is copied to `test/apps/tmp/` so that any changes won't affect the pre-generated app (this saves us having to regenerate the app on each run). If tests are failing and you don't know why, it might be that the pre-generated app has become inconsistent in some way. In that case the best solution is to purge it with `rm -rf test/apps/*` and then run the acceptance tests again, which will generate a new app. ## Testing different Rails versions You can set the `RAILS_VERSION` environment variable: ``` $ RAILS_VERSION="~> 3.2.0" rake test:acceptance ``` The apps in `test/apps` will be named based on the rails version and the Spring version. ## Testing with your app You cannot link to a git repo from your Gemfile. Spring doesn't support this due to the way that it gets loaded (bypassing bundler for performance reasons). Therefore, to test changes with your app, run `rake install` to properly install the gem on your system. ## Submitting a pull request If your change is a bugfix or feature, please make sure you add to `CHANGELOG.md` under the "Next Release" heading (add the heading if needed). spring-2.1.1/Gemfile000066400000000000000000000002441372105127500143130ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in spring.gemspec gemspec if ENV["RAILS_VERSION"] gem "activesupport", ENV["RAILS_VERSION"] end spring-2.1.1/LICENSE.txt000066400000000000000000000020621372105127500146430ustar00rootroot00000000000000Copyright (c) 2012-2017 Jon Leighton MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. spring-2.1.1/README.md000066400000000000000000000332751372105127500143110ustar00rootroot00000000000000# Spring [![Build Status](https://travis-ci.org/rails/spring.svg?branch=master)](https://travis-ci.org/rails/spring) [![Gem Version](https://badge.fury.io/rb/spring.svg)](http://badge.fury.io/rb/spring) Spring is a Rails application preloader. It speeds up development by keeping your application running in the background so you don't need to boot it every time you run a test, rake task or migration. ## Features * Totally automatic; no need to explicitly start and stop the background process * Reloads your application code on each run * Restarts your application when configs / initializers / gem dependencies are changed ## Compatibility * Ruby versions: MRI 2.4, MRI 2.5, MRI 2.6 * Rails versions: 4.2, 5.0, 5.1, 5.2, 6.0 (Spring is installed by default when you do `rails new` to generate your application) Spring makes extensive use of `Process.fork`, so won't be able to provide a speed up on platforms which don't support forking (Windows, JRuby). ## Walkthrough ### Setup Add Spring to your Gemfile: ``` ruby gem "spring", group: :development ``` (Note: using `gem "spring", git: "..."` *won't* work and is not a supported way of using Spring.) It's recommended to 'springify' the executables in your `bin/` directory: ``` $ bundle install $ bundle exec spring binstub --all ``` This generates a `bin/spring` executable, and inserts a small snippet of code into relevant existing executables. The snippet looks like this: ``` ruby begin load File.expand_path('../spring', __FILE__) rescue LoadError end ``` On platforms where Spring is installed and supported, this snippet hooks Spring into the execution of commands. In other cases, the snippet will just be silently ignored and the lines after it will be executed as normal. If you don't want to prefix every command you type with `bin/`, you can [use direnv](https://github.com/direnv/direnv#the-stdlib) to automatically add `./bin` to your `PATH` when you `cd` into your application. Simply create an `.envrc` file with the command `PATH_add bin` in your Rails directory. ### Usage For this walkthrough I've generated a new Rails application, and run `rails generate scaffold post name:string`. Let's run a test: ``` $ time bin/rake test test/controllers/posts_controller_test.rb Running via Spring preloader in process 2734 Run options: # Running tests: ....... Finished tests in 0.127245s, 55.0121 tests/s, 78.5887 assertions/s. 7 tests, 10 assertions, 0 failures, 0 errors, 0 skips real 0m2.165s user 0m0.281s sys 0m0.066s ``` That wasn't particularly fast because it was the first run, so Spring had to boot the application. It's now running: ``` $ bin/spring status Spring is running: 26150 spring server | spring-demo-app | started 3 secs ago 26155 spring app | spring-demo-app | started 3 secs ago | test mode ``` The next run is faster: ``` $ time bin/rake test test/controllers/posts_controller_test.rb Running via Spring preloader in process 8352 Run options: # Running tests: ....... Finished tests in 0.176896s, 39.5714 tests/s, 56.5305 assertions/s. 7 tests, 10 assertions, 0 failures, 0 errors, 0 skips real 0m0.610s user 0m0.276s sys 0m0.059s ``` If we edit any of the application files, or test files, the changes will be picked up on the next run without the background process having to restart. This works in exactly the same way as the code reloading which allows you to refresh your browser and instantly see changes during development. But if we edit any of the files which were used to start the application (configs, initializers, your gemfile), the application needs to be fully restarted. This happens automatically. Let's "edit" `config/application.rb`: ``` $ touch config/application.rb $ bin/spring status Spring is running: 26150 spring server | spring-demo-app | started 36 secs ago 26556 spring app | spring-demo-app | started 1 sec ago | test mode ``` The application detected that `config/application.rb` changed and automatically restarted itself. If we run a command that uses a different environment, then that environment gets booted up: ``` $ bin/rake routes Running via Spring preloader in process 2363 posts GET /posts(.:format) posts#index POST /posts(.:format) posts#create new_post GET /posts/new(.:format) posts#new edit_post GET /posts/:id/edit(.:format) posts#edit post GET /posts/:id(.:format) posts#show PUT /posts/:id(.:format) posts#update DELETE /posts/:id(.:format) posts#destroy $ bin/spring status Spring is running: 26150 spring server | spring-demo-app | started 1 min ago 26556 spring app | spring-demo-app | started 42 secs ago | test mode 26707 spring app | spring-demo-app | started 2 secs ago | development mode ``` There's no need to "shut down" Spring. This will happen automatically when you close your terminal. However if you do want to do a manual shut down, use the `stop` command: ``` $ bin/spring stop Spring stopped. ``` From within your code, you can check whether Spring is active with `if defined?(Spring)`. ### Removal To remove Spring: * 'Unspring' your bin/ executables: `bin/spring binstub --remove --all` * Remove spring from your Gemfile ### Deployment You must not install Spring on your production environment. To prevent it from being installed, provide the `--without development test` argument to the `bundle install` command which is used to install gems on your production machines: ``` $ bundle install --without development test ``` ## Commands ### `rake` Runs a rake task. Rake tasks run in the `development` environment by default. You can change this on the fly by using the `RAILS_ENV` environment variable. The environment is also configurable with the `Spring::Commands::Rake.environment_matchers` hash. This has sensible defaults, but if you need to match a specific task to a specific environment, you'd do it like this: ``` ruby Spring::Commands::Rake.environment_matchers["perf_test"] = "test" Spring::Commands::Rake.environment_matchers[/^perf/] = "test" # To change the environment when you run `rake` with no arguments Spring::Commands::Rake.environment_matchers[:default] = "development" ``` ### `rails console`, `rails generate`, `rails runner` These execute the rails command you already know and love. If you run a different sub command (e.g. `rails server`) then Spring will automatically pass it through to the underlying `rails` executable (without the speed-up). ### Additional commands You can add these to your Gemfile for additional commands: * [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec) * [spring-commands-cucumber](https://github.com/jonleighton/spring-commands-cucumber) * [spring-commands-spinach](https://github.com/jvanbaarsen/spring-commands-spinach) * [spring-commands-testunit](https://github.com/jonleighton/spring-commands-testunit) - useful for running `Test::Unit` tests on Rails 3, since only Rails 4 allows you to use `rake test path/to/test` to run a particular test/directory. * [spring-commands-parallel-tests](https://github.com/DocSpring/spring-commands-parallel-tests) - Adds the `parallel_*` commands from [`parallel_tests`](https://github.com/grosser/parallel_tests). * [spring-commands-teaspoon](https://github.com/alejandrobabio/spring-commands-teaspoon.git) * [spring-commands-m](https://github.com/gabrieljoelc/spring-commands-m.git) * [spring-commands-rubocop](https://github.com/p0deje/spring-commands-rubocop) * [spring-commands-rackup](https://github.com/wintersolutions/spring-commands-rackup) * [spring-commands-rack-console](https://github.com/wintersolutions/spring-commands-rack-console) ## Use without adding to bundle If you don't want Spring-related code checked into your source repository, it's possible to use Spring without adding to your Gemfile. However, using Spring binstubs without adding Spring to the Gemfile is not supported. To use Spring like this, do a `gem install spring` and then prefix commands with `spring`. For example, rather than running `bin/rake -T`, you'd run `spring rake -T`. ## Temporarily disabling Spring If you're using Spring binstubs, but temporarily don't want commands to run through Spring, set the `DISABLE_SPRING` environment variable. ## Class reloading Spring uses Rails' class reloading mechanism (`ActiveSupport::Dependencies`) to keep your code up to date between test runs. This is the same mechanism which allows you to see changes during development when you refresh the page. However, you may never have used this mechanism with your `test` environment before, and this can cause problems. It's important to realise that code reloading means that the constants in your application are *different objects* after files have changed: ``` $ bin/rails runner 'puts User.object_id' 70127987886040 $ touch app/models/user.rb $ bin/rails runner 'puts User.object_id' 70127976764620 ``` Suppose you have an initializer `config/initializers/save_user_class.rb` like so: ``` ruby USER_CLASS = User ``` This saves off the *first* version of the `User` class, which will not be the same object as `User` after the code has been reloaded: ``` $ bin/rails runner 'puts User == USER_CLASS' true $ touch app/models/user.rb $ bin/rails runner 'puts User == USER_CLASS' false ``` So to avoid this problem, don't save off references to application constants in your initialization code. ## Using Spring with a containerized development environment As of Spring 1.7, there is some support for doing this. See [this example repository](https://github.com/jonleighton/spring-docker-example) for information about how to do it with [Docker](https://www.docker.com/). ## Configuration Spring will read `~/.spring.rb` and `config/spring.rb` for custom settings. Note that `~/.spring.rb` is loaded *before* bundler, but `config/spring.rb` is loaded *after* bundler. So if you have any `spring-commands-*` gems installed that you want to be available in all projects without having to be added to the project's Gemfile, require them in your `~/.spring.rb`. `config/spring_client.rb` is also loaded before bundler and before a server process is started, it can be used to add new top-level commands. ### Application root Spring must know how to find your Rails application. If you have a normal app everything works out of the box. If you are working on a project with a special setup (an engine for example), you must tell Spring where your app is located: ```ruby Spring.application_root = './test/dummy' ``` ### Running code before forking There is no `Spring.before_fork` callback. To run something before the fork, you can place it in `~/.spring.rb` or `config/spring.rb` or in any of the files which get run when your application initializes, such as `config/application.rb`, `config/environments/*.rb` or `config/initializers/*.rb`. ### Running code after forking You might want to run code after Spring forked off the process but before the actual command is run. You might want to use an `after_fork` callback if you have to connect to an external service, do some general cleanup or set up dynamic configuration. ```ruby Spring.after_fork do # run arbitrary code end ``` If you want to register multiple callbacks you can simply call `Spring.after_fork` multiple times with different blocks. ### Watching files and directories Spring will automatically detect file changes to any file loaded when the server boots. Changes will cause the affected environments to be restarted. If there are additional files or directories which should trigger an application restart, you can specify them with `Spring.watch`: ```ruby Spring.watch "config/some_config_file.yml" ``` By default Spring polls the filesystem for changes once every 0.2 seconds. This method requires zero configuration, but if you find that it's using too much CPU, then you can use event-based file system listening by installing the [spring-watcher-listen](https://github.com/jonleighton/spring-watcher-listen) gem. ### Quiet output To disable the "Running via Spring preloader" message which is shown each time a command runs: ``` ruby Spring.quiet = true ``` ### Environment variables The following environment variables are used by Spring: * `DISABLE_SPRING` - If set, Spring will be bypassed and your application will boot in a foreground process * `SPRING_LOG` - The path to a file which Spring will write log messages to. * `SPRING_TMP_PATH` - The directory where Spring should write its temporary files (a pidfile and a socket). By default we use the `XDG_RUNTIME_DIR` environment variable, or else `Dir.tmpdir`, and then create a directory in that named `spring-$UID`. We don't use your Rails application's `tmp/` directory because that may be on a filesystem which doesn't support UNIX sockets. * `SPRING_APPLICATION_ID` - Used to identify distinct Rails applications. By default it is an MD5 hash of the current `RUBY_VERSION`, and the path to your Rails project root. * `SPRING_SOCKET` - The path which should be used for the UNIX socket which Spring uses to communicate with the long-running Spring server process. By default this is `SPRING_TMP_PATH/SPRING_APPLICATION_ID`. * `SPRING_PIDFILE` - The path which should be used to store the pid of the long-running Spring server process. By default this is related to the socket path; if the socket path is `/foo/bar/spring.sock` the pidfile will be `/foo/bar/spring.pid`. * `SPRING_SERVER_COMMAND` - The command to run to start up the Spring server when it is not already running. Defaults to `spring _[version]_ server --background`. ## Troubleshooting If you want to get more information about what Spring is doing, you can run Spring explicitly in a separate terminal: ``` $ spring server ``` Logging output will be printed to stdout. You can also send log output to a file with the `SPRING_LOG` environment variable. spring-2.1.1/Rakefile000066400000000000000000000006741372105127500144740ustar00rootroot00000000000000require "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" require "bump/tasks" namespace :test do Rake::TestTask.new(:unit) do |t| t.test_files = FileList["test/unit/**/*_test.rb"] t.verbose = true end Rake::TestTask.new(:acceptance) do |t| t.test_files = FileList["test/acceptance_test.rb"] t.verbose = true end desc 'run all tests' task all: [:unit, :acceptance] end task default: 'test:all' spring-2.1.1/bin/000077500000000000000000000000001372105127500135705ustar00rootroot00000000000000spring-2.1.1/bin/spring000077500000000000000000000037021372105127500150220ustar00rootroot00000000000000#!/usr/bin/env ruby if defined?(Spring) $stderr.puts "You've tried to invoke Spring when it's already loaded (i.e. the Spring " \ "constant is defined)." $stderr.puts $stderr.puts "This is probably because you generated binstubs with " \ "Spring 1.0, and you now have a Spring version > 1.0 on your system. To solve " \ "this, upgrade your bundle to the latest Spring version and then run " \ "`bundle exec spring binstub --all` to regenerate your binstubs. This is a one-time " \ "step necessary to upgrade from 1.0 to 1.1." $stderr.puts $stderr.puts "Here's the backtrace:" $stderr.puts $stderr.puts caller exit 1 end if defined?(Gem) if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.1.0") warn "Warning: You're using Rubygems #{Gem::VERSION} with Spring. " \ "Upgrade to at least Rubygems 2.1.0 and run `gem pristine --all` for better " \ "startup performance." else stubs = Gem::Specification.stubs.grep(Gem::StubSpecification) # stubbed? method added in https://github.com/rubygems/rubygems/pull/694 if Gem::Specification.stubs.first.respond_to?(:stubbed?) unstubbed = stubs.reject(&:stubbed?) else unstubbed = stubs.reject { |s| s.send(:data).is_a?(Gem::StubSpecification::StubLine) } end # `gem pristine --all` ignores default gems. it doesn't really matter, # as there are probably not many of them on the system. unstubbed.reject!(&:default_gem?) if unstubbed.any? warn "Warning: Running `gem pristine --all` to regenerate your installed gemspecs " \ "(and deleting then reinstalling your bundle if you use bundle --path) " \ "will improve the startup performance of Spring." end end end lib = File.expand_path("../../lib", __FILE__) $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) # enable local development require 'spring/client' Spring::Client.run(ARGV) spring-2.1.1/lib/000077500000000000000000000000001372105127500135665ustar00rootroot00000000000000spring-2.1.1/lib/spring/000077500000000000000000000000001372105127500150705ustar00rootroot00000000000000spring-2.1.1/lib/spring/application.rb000066400000000000000000000235561372105127500177330ustar00rootroot00000000000000require "spring/boot" require "set" require "pty" module Spring class Application attr_reader :manager, :watcher, :spring_env, :original_env def initialize(manager, original_env, spring_env = Env.new) @manager = manager @original_env = original_env @spring_env = spring_env @mutex = Mutex.new @waiting = Set.new @preloaded = false @state = :initialized @interrupt = IO.pipe end def state(val) return if exiting? log "#{@state} -> #{val}" @state = val end def state!(val) state val @interrupt.last.write "." end def app_env ENV['RAILS_ENV'] end def app_name spring_env.app_name end def log(message) spring_env.log "[application:#{app_env}] #{message}" end def preloaded? @preloaded end def preload_failed? @preloaded == :failure end def exiting? @state == :exiting end def terminating? @state == :terminating end def watcher_stale? @state == :watcher_stale end def initialized? @state == :initialized end def start_watcher @watcher = Spring.watcher @watcher.on_stale do state! :watcher_stale end if @watcher.respond_to? :on_debug @watcher.on_debug do |message| spring_env.log "[watcher:#{app_env}] #{message}" end end @watcher.start end def preload log "preloading app" begin require "spring/commands" ensure start_watcher end require Spring.application_root_path.join("config", "application") unless Rails.respond_to?(:gem_version) && Rails.gem_version >= Gem::Version.new('4.2.0') raise "Spring only supports Rails >= 4.2.0" end # config/environments/test.rb will have config.cache_classes = true. However # we want it to be false so that we can reload files. This is a hack to # override the effect of config.cache_classes = true. We can then actually # set config.cache_classes = false after loading the environment. Rails::Application.initializer :initialize_dependency_mechanism, group: :all do ActiveSupport::Dependencies.mechanism = :load end require Spring.application_root_path.join("config", "environment") @original_cache_classes = Rails.application.config.cache_classes Rails.application.config.cache_classes = false disconnect_database @preloaded = :success rescue Exception => e @preloaded = :failure watcher.add e.backtrace.map { |line| line[/^(.*)\:\d+/, 1] } raise e unless initialized? ensure watcher.add loaded_application_features watcher.add Spring.gemfile, "#{Spring.gemfile}.lock" if defined?(Rails) && Rails.application watcher.add Rails.application.paths["config/initializers"] watcher.add Rails.application.paths["config/database"] if secrets_path = Rails.application.paths["config/secrets"] watcher.add secrets_path end end end def eager_preload with_pty { preload } end def run state :running manager.puts loop do IO.select [manager, @interrupt.first] if terminating? || watcher_stale? || preload_failed? exit else serve manager.recv_io(UNIXSocket) end end end def serve(client) log "got client" manager.puts _stdout, stderr, _stdin = streams = 3.times.map { client.recv_io } [STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) } preload unless preloaded? args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env") command = Spring.command(args.shift) connect_database setup command if Rails.application.reloaders.any?(&:updated?) # Rails 5.1 forward-compat. AD::R is deprecated to AS::R in Rails 5. if defined? ActiveSupport::Reloader Rails.application.reloader.reload! else ActionDispatch::Reloader.cleanup! ActionDispatch::Reloader.prepare! end end # Ensure we boot the process in the directory the command was called from, # not from the directory Spring started in original_dir = Dir.pwd Dir.chdir(env['PWD'] || original_dir) pid = fork { Process.setsid IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") } trap("TERM", "DEFAULT") unless Spring.quiet STDERR.puts "Running via Spring preloader in process #{Process.pid}" if Rails.env.production? STDERR.puts "WARNING: Spring is running in production. To fix " \ "this make sure the spring gem is only present " \ "in `development` and `test` groups in your Gemfile " \ "and make sure you always use " \ "`bundle install --without development test` in production" end end ARGV.replace(args) $0 = command.exec_name # Delete all env vars which are unchanged from before Spring started original_env.each { |k, v| ENV.delete k if ENV[k] == v } # Load in the current env vars, except those which *were* changed when Spring started env.each { |k, v| ENV[k] ||= v } # requiring is faster, so if config.cache_classes was true in # the environment's config file, then we can respect that from # here on as we no longer need constant reloading. if @original_cache_classes ActiveSupport::Dependencies.mechanism = :require Rails.application.config.cache_classes = true end connect_database srand invoke_after_fork_callbacks shush_backtraces command.call } disconnect_database log "forked #{pid}" manager.puts pid wait pid, streams, client rescue Exception => e log "exception: #{e}" manager.puts unless pid if streams && !e.is_a?(SystemExit) print_exception(stderr, e) streams.each(&:close) end client.puts(1) if pid client.close ensure # Redirect STDOUT and STDERR to prevent from keeping the original FDs # (i.e. to prevent `spring rake -T | grep db` from hanging forever), # even when exception is raised before forking (i.e. preloading). reset_streams Dir.chdir(original_dir) end def terminate if exiting? # Ensure that we do not ignore subsequent termination attempts log "forced exit" @waiting.each { |pid| Process.kill("TERM", pid) } Kernel.exit else state! :terminating end end def exit state :exiting manager.shutdown(:RDWR) exit_if_finished sleep end def exit_if_finished @mutex.synchronize { Kernel.exit if exiting? && @waiting.empty? } end # The command might need to require some files in the # main process so that they are cached. For example a test command wants to # load the helper file once and have it cached. def setup(command) if command.setup watcher.add loaded_application_features # loaded features may have changed end end def invoke_after_fork_callbacks Spring.after_fork_callbacks.each do |callback| callback.call end end def loaded_application_features root = Spring.application_root_path.to_s $LOADED_FEATURES.select { |f| f.start_with?(root) } end def disconnect_database ActiveRecord::Base.remove_connection if active_record_configured? end def connect_database ActiveRecord::Base.establish_connection if active_record_configured? end # This feels very naughty def shush_backtraces Kernel.module_eval do old_raise = Kernel.method(:raise) remove_method :raise define_method :raise do |*args| begin old_raise.call(*args) ensure if $! lib = File.expand_path("..", __FILE__) $!.backtrace.reject! { |line| line.start_with?(lib) } end end end private :raise end end def print_exception(stream, error) first, rest = error.backtrace.first, error.backtrace.drop(1) stream.puts("#{first}: #{error} (#{error.class})") rest.each { |line| stream.puts("\tfrom #{line}") } end def with_pty PTY.open do |master, slave| [STDOUT, STDERR, STDIN].each { |s| s.reopen slave } reader_thread = Spring.failsafe_thread { master.read } begin yield ensure reader_thread.kill reset_streams end end end def reset_streams [STDOUT, STDERR].each { |stream| stream.reopen(spring_env.log_file) } STDIN.reopen("/dev/null") end def wait(pid, streams, client) @mutex.synchronize { @waiting << pid } # Wait in a separate thread so we can run multiple commands at once Spring.failsafe_thread { begin _, status = Process.wait2 pid log "#{pid} exited with #{status.exitstatus}" streams.each(&:close) client.puts(status.exitstatus) client.close ensure @mutex.synchronize { @waiting.delete pid } exit_if_finished end } Spring.failsafe_thread { while signal = client.gets.chomp begin Process.kill(signal, -Process.getpgid(pid)) client.puts(0) rescue Errno::ESRCH client.puts(1) end end } end private def active_record_configured? defined?(ActiveRecord::Base) && ActiveRecord::Base.configurations.any? end end end spring-2.1.1/lib/spring/application/000077500000000000000000000000001372105127500173735ustar00rootroot00000000000000spring-2.1.1/lib/spring/application/boot.rb000066400000000000000000000010071372105127500206610ustar00rootroot00000000000000# This is necessary for the terminal to work correctly when we reopen stdin. Process.setsid require "spring/application" app = Spring::Application.new( UNIXSocket.for_fd(3), Spring::JSON.load(ENV.delete("SPRING_ORIGINAL_ENV").dup), Spring::Env.new(log_file: IO.for_fd(4)) ) Signal.trap("TERM") { app.terminate } Spring::ProcessTitleUpdater.run { |distance| "spring app | #{app.app_name} | started #{distance} ago | #{app.app_env} mode" } app.eager_preload if ENV.delete("SPRING_PRELOAD") == "1" app.run spring-2.1.1/lib/spring/application_manager.rb000066400000000000000000000065061372105127500214210ustar00rootroot00000000000000module Spring class ApplicationManager attr_reader :pid, :child, :app_env, :spring_env, :status def initialize(app_env, spring_env) @app_env = app_env @spring_env = spring_env @mutex = Mutex.new @state = :running @pid = nil end def log(message) spring_env.log "[application_manager:#{app_env}] #{message}" end # We're not using @mutex.synchronize to avoid the weird ":10" # line which messes with backtraces in e.g. rspec def synchronize @mutex.lock yield ensure @mutex.unlock end def start start_child end def restart return if @state == :stopping start_child(true) end def alive? @pid end def with_child synchronize do if alive? begin yield rescue Errno::ECONNRESET, Errno::EPIPE # The child has died but has not been collected by the wait thread yet, # so start a new child and try again. log "child dead; starting" start yield end else log "child not running; starting" start yield end end end # Returns the pid of the process running the command, or nil if the application process died. def run(client) with_child do child.send_io client child.gets or raise Errno::EPIPE end pid = child.gets.to_i unless pid.zero? log "got worker pid #{pid}" pid end rescue Errno::ECONNRESET, Errno::EPIPE => e log "#{e} while reading from child; returning no pid" nil ensure client.close end def stop log "stopping" @state = :stopping if pid Process.kill('TERM', pid) Process.wait(pid) end rescue Errno::ESRCH, Errno::ECHILD # Don't care end private def start_child(preload = false) @child, child_socket = UNIXSocket.pair Bundler.with_original_env do bundler_dir = File.expand_path("../..", $LOADED_FEATURES.grep(/bundler\/setup\.rb$/).first) @pid = Process.spawn( { "RAILS_ENV" => app_env, "RACK_ENV" => app_env, "SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV), "SPRING_PRELOAD" => preload ? "1" : "0" }, "ruby", *(bundler_dir != RbConfig::CONFIG["rubylibdir"] ? ["-I", bundler_dir] : []), "-I", File.expand_path("../..", __FILE__), "-e", "require 'spring/application/boot'", 3 => child_socket, 4 => spring_env.log_file, ) end start_wait_thread(pid, child) if child.gets child_socket.close end def start_wait_thread(pid, child) Process.detach(pid) Spring.failsafe_thread { # The recv can raise an ECONNRESET, killing the thread, but that's ok # as if it does we're no longer interested in the child loop do IO.select([child]) break if child.recv(1, Socket::MSG_PEEK).empty? sleep 0.01 end log "child #{pid} shutdown" synchronize { if @pid == pid @pid = nil restart end } } end end end spring-2.1.1/lib/spring/binstub.rb000066400000000000000000000004661372105127500170710ustar00rootroot00000000000000command = File.basename($0) bin_path = File.expand_path("../../../bin/spring", __FILE__) if command == "spring" load bin_path else disable = ENV["DISABLE_SPRING"] if Process.respond_to?(:fork) && (disable.nil? || disable.empty? || disable == "0") ARGV.unshift(command) load bin_path end end spring-2.1.1/lib/spring/boot.rb000066400000000000000000000003171372105127500163610ustar00rootroot00000000000000require "socket" require "thread" require "spring/configuration" require "spring/env" require "spring/process_title_updater" require "spring/json" require "spring/watcher" require "spring/failsafe_thread" spring-2.1.1/lib/spring/client.rb000066400000000000000000000022461372105127500166770ustar00rootroot00000000000000require "spring/errors" require "spring/json" require "spring/client/command" require "spring/client/run" require "spring/client/help" require "spring/client/binstub" require "spring/client/stop" require "spring/client/status" require "spring/client/rails" require "spring/client/version" require "spring/client/server" module Spring module Client COMMANDS = { "help" => Client::Help, "-h" => Client::Help, "--help" => Client::Help, "binstub" => Client::Binstub, "stop" => Client::Stop, "status" => Client::Status, "rails" => Client::Rails, "-v" => Client::Version, "--version" => Client::Version, "server" => Client::Server, } def self.run(args) command_for(args.first).call(args) rescue CommandNotFound Client::Help.call(args) rescue ClientError => e $stderr.puts e.message exit 1 end def self.command_for(name) COMMANDS[name] || Client::Run end end end # allow users to add hooks that do not run in the server # or modify start/stop if File.exist?("config/spring_client.rb") require "./config/spring_client.rb" end spring-2.1.1/lib/spring/client/000077500000000000000000000000001372105127500163465ustar00rootroot00000000000000spring-2.1.1/lib/spring/client/binstub.rb000066400000000000000000000140411372105127500203410ustar00rootroot00000000000000require 'set' module Spring module Client class Binstub < Command SHEBANG = /\#\!.*\n(\#.*\n)*/ # If loading the bin/spring file works, it'll run Spring which will # eventually call Kernel.exit. This means that in the client process # we will never execute the lines after this block. But if the Spring # client is not invoked for whatever reason, then the Kernel.exit won't # happen, and so we'll fall back to the lines after this block, which # should cause the "unsprung" version of the command to run. LOADER = < e raise unless e.message.include?('spring') end CODE # The defined? check ensures these lines don't execute when we load the # binstub from the application process. Which means that in the application # process we'll execute the lines which come after the LOADER block, which # is what we want. SPRING = <<'CODE' #!/usr/bin/env ruby # This file loads Spring without using Bundler, in order to be fast. # It gets overwritten when you run the `spring binstub` command. unless defined?(Spring) require 'rubygems' require 'bundler' lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) spring = lockfile.specs.detect { |spec| spec.name == 'spring' } if spring Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path gem 'spring', spring.version require 'spring/binstub' end end CODE OLD_BINSTUB = %{if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?} BINSTUB_VARIATIONS = Regexp.union [ %{begin\n load File.expand_path('../spring', __FILE__)\nrescue LoadError\nend\n}, %{begin\n spring_bin_path = File.expand_path('../spring', __FILE__)\n load spring_bin_path\nrescue LoadError => e\n raise unless e.message.end_with? spring_bin_path, 'spring/binstub'\nend\n}, LOADER ].map { |binstub| /#{Regexp.escape(binstub).gsub("'", "['\"]")}/ } class Item attr_reader :command, :existing def initialize(command) @command = command if command.binstub.exist? @existing = command.binstub.read elsif command.name == "rails" scriptfile = Spring.application_root_path.join("script/rails") @existing = scriptfile.read if scriptfile.exist? end end def status(text, stream = $stdout) stream.puts "* #{command.binstub_name}: #{text}" end def add if existing if existing.include?(OLD_BINSTUB) fallback = existing.match(/#{Regexp.escape OLD_BINSTUB}\n(.*)else/m)[1] fallback.gsub!(/^ /, "") fallback = nil if fallback.include?("exec") generate(fallback) status "upgraded" elsif existing.include?(LOADER) status "Spring already present" elsif existing =~ BINSTUB_VARIATIONS upgraded = existing.sub(BINSTUB_VARIATIONS, LOADER) File.write(command.binstub, upgraded) status "upgraded" else head, shebang, tail = existing.partition(SHEBANG) if shebang.include?("ruby") unless command.binstub.exist? FileUtils.touch command.binstub command.binstub.chmod 0755 end File.write(command.binstub, "#{head}#{shebang}#{LOADER}#{tail}") status "Spring inserted" else status "doesn't appear to be ruby, so cannot use Spring", $stderr exit 1 end end else generate status "generated with Spring" end end def generate(fallback = nil) unless fallback fallback = "require 'bundler/setup'\n" \ "load Gem.bin_path('#{command.gem_name}', '#{command.exec_name}')\n" end File.write(command.binstub, "#!/usr/bin/env ruby\n#{LOADER}#{fallback}") command.binstub.chmod 0755 end def remove if existing File.write(command.binstub, existing.sub(BINSTUB_VARIATIONS, "")) status "Spring removed" end end end attr_reader :bindir, :items def self.description "Generate Spring based binstubs. Use --all to generate a binstub for all known commands. Use --remove to revert." end def self.rails_command @rails_command ||= CommandWrapper.new("rails") end def self.call(args) require "spring/commands" super end def initialize(args) super @bindir = env.root.join("bin") @all = false @mode = :add @items = args.drop(1) .map { |name| find_commands name } .inject(Set.new, :|) .map { |command| Item.new(command) } end def find_commands(name) case name when "--all" @all = true commands = Spring.commands.dup commands.delete_if { |command_name, _| command_name.start_with?("rails_") } commands.values + [self.class.rails_command] when "--remove" @mode = :remove [] when "rails" [self.class.rails_command] else if command = Spring.commands[name] [command] else $stderr.puts "The '#{name}' command is not known to spring." exit 1 end end end def call case @mode when :add bindir.mkdir unless bindir.exist? File.write(spring_binstub, SPRING) spring_binstub.chmod 0755 items.each(&:add) when :remove spring_binstub.delete if @all items.each(&:remove) else raise ArgumentError end end def spring_binstub bindir.join("spring") end end end end spring-2.1.1/lib/spring/client/command.rb000066400000000000000000000004051372105127500203100ustar00rootroot00000000000000require "spring/env" module Spring module Client class Command def self.call(args) new(args).call end attr_reader :args, :env def initialize(args) @args = args @env = Env.new end end end end spring-2.1.1/lib/spring/client/help.rb000066400000000000000000000030601372105127500176220ustar00rootroot00000000000000require "spring/version" module Spring module Client class Help < Command attr_reader :spring_commands, :application_commands def self.description "Print available commands." end def self.call(args) require "spring/commands" super end def initialize(args, spring_commands = nil, application_commands = nil) super args @spring_commands = spring_commands || Spring::Client::COMMANDS.dup @application_commands = application_commands || Spring.commands.dup @spring_commands.delete_if { |k, v| k.start_with?("-") } @application_commands["rails"] = @spring_commands.delete("rails") end def call puts formatted_help end def formatted_help ["Version: #{env.version}\n", "Usage: spring COMMAND [ARGS]\n", *command_help("Spring itself", spring_commands), '', *command_help("your application", application_commands)].join("\n") end def command_help(subject, commands) ["Commands for #{subject}:\n", *commands.sort_by(&:first).map { |name, command| display(name, command) }.compact] end private def all_commands spring_commands.merge application_commands end def display(name, command) if command.description " #{name.ljust(max_name_width)} #{command.description}" end end def max_name_width @max_name_width ||= all_commands.keys.map(&:length).max end end end end spring-2.1.1/lib/spring/client/rails.rb000066400000000000000000000014641372105127500200120ustar00rootroot00000000000000require "set" module Spring module Client class Rails < Command COMMANDS = Set.new %w(console runner generate destroy test) ALIASES = { "c" => "console", "r" => "runner", "g" => "generate", "d" => "destroy", "t" => "test" } def self.description "Run a rails command. The following sub commands will use Spring: #{COMMANDS.to_a.join ', '}." end def call command_name = ALIASES[args[1]] || args[1] if COMMANDS.include?(command_name) Run.call(["rails_#{command_name}", *args.drop(2)]) else require "spring/configuration" ARGV.shift load Dir.glob(Spring.application_root_path.join("{bin,script}/rails")).first exit end end end end end spring-2.1.1/lib/spring/client/run.rb000066400000000000000000000127461372105127500175110ustar00rootroot00000000000000require "rbconfig" require "socket" require "bundler" module Spring module Client class Run < Command FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO WINCH) & Signal.list.keys CONNECT_TIMEOUT = 1 BOOT_TIMEOUT = 20 attr_reader :server def initialize(args) super @signal_queue = [] @server_booted = false end def log(message) env.log "[client] #{message}" end def connect @server = UNIXSocket.open(env.socket_name) end def call begin connect rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED cold_run else warm_run end ensure server.close if server end def warm_run run rescue CommandNotFound require "spring/commands" if Spring.command?(args.first) # Command installed since Spring started stop_server cold_run else raise end end def cold_run boot_server connect run end def run verify_server_version application, client = UNIXSocket.pair queue_signals connect_to_application(client) run_command(client, application) rescue Errno::ECONNRESET exit 1 end def boot_server env.socket_path.unlink if env.socket_path.exist? pid = Process.spawn(gem_env, env.server_command, out: File::NULL) timeout = Time.now + BOOT_TIMEOUT @server_booted = true until env.socket_path.exist? _, status = Process.waitpid2(pid, Process::WNOHANG) if status exit status.exitstatus elsif Time.now > timeout $stderr.puts "Starting Spring server with `#{env.server_command}` " \ "timed out after #{BOOT_TIMEOUT} seconds" exit 1 end sleep 0.1 end end def server_booted? @server_booted end def gem_env bundle = Bundler.bundle_path.to_s paths = Gem.path + ENV["GEM_PATH"].to_s.split(File::PATH_SEPARATOR) { "GEM_PATH" => [bundle, *paths].uniq.join(File::PATH_SEPARATOR), "GEM_HOME" => bundle } end def stop_server server.close @server = nil env.stop end def verify_server_version server_version = server.gets.chomp if server_version != env.version $stderr.puts "There is a version mismatch between the Spring client " \ "(#{env.version}) and the server (#{server_version})." if server_booted? $stderr.puts "We already tried to reboot the server, but the mismatch is still present." exit 1 else $stderr.puts "Restarting to resolve." stop_server cold_run end end end def connect_to_application(client) server.send_io client send_json server, "args" => args, "default_rails_env" => default_rails_env if IO.select([server], [], [], CONNECT_TIMEOUT) server.gets or raise CommandNotFound else raise "Error connecting to Spring server" end end def run_command(client, application) log "sending command" application.send_io STDOUT application.send_io STDERR application.send_io STDIN send_json application, "args" => args, "env" => ENV.to_hash pid = server.gets pid = pid.chomp if pid # We must not close the client socket until we are sure that the application has # received the FD. Otherwise the FD can end up getting closed while it's in the server # socket buffer on OS X. This doesn't happen on Linux. client.close if pid && !pid.empty? log "got pid: #{pid}" suspend_resume_on_tstp_cont(pid) forward_signals(application) status = application.read.to_i log "got exit status #{status}" exit status else log "got no pid" exit 1 end ensure application.close end def queue_signals FORWARDED_SIGNALS.each do |sig| trap(sig) { @signal_queue << sig } end end def suspend_resume_on_tstp_cont(pid) trap("TSTP") { log "suspended" Process.kill("STOP", pid.to_i) Process.kill("STOP", Process.pid) } trap("CONT") { log "resumed" Process.kill("CONT", pid.to_i) } end def forward_signals(application) @signal_queue.each { |sig| kill sig, application } FORWARDED_SIGNALS.each do |sig| trap(sig) { forward_signal sig, application } end end def forward_signal(sig, application) if kill(sig, application) != 0 # If the application process is gone, then don't block the # signal on this process. trap(sig, 'DEFAULT') Process.kill(sig, Process.pid) end end def kill(sig, application) application.puts(sig) application.gets.to_i end def send_json(socket, data) data = JSON.dump(data) socket.puts data.bytesize socket.write data end def default_rails_env ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' end end end end spring-2.1.1/lib/spring/client/server.rb000066400000000000000000000005441372105127500202040ustar00rootroot00000000000000module Spring module Client class Server < Command def self.description "Explicitly start a Spring server in the foreground" end def call require "spring/server" Spring::Server.boot(foreground: foreground?) end def foreground? !args.include?("--background") end end end end spring-2.1.1/lib/spring/client/status.rb000066400000000000000000000012751372105127500202230ustar00rootroot00000000000000module Spring module Client class Status < Command def self.description "Show current status." end def call if env.server_running? puts "Spring is running:" puts print_process env.pid application_pids.each { |pid| print_process pid } else puts "Spring is not running." end end def print_process(pid) puts `ps -p #{pid} -o pid= -o command=` end def application_pids candidates = `ps -ax -o ppid= -o pid=`.lines candidates.select { |l| l =~ /^(\s+)?#{env.pid} / } .map { |l| l.split(" ").last } end end end end spring-2.1.1/lib/spring/client/stop.rb000066400000000000000000000007071372105127500176640ustar00rootroot00000000000000require "spring/version" module Spring module Client class Stop < Command def self.description "Stop all Spring processes for this project." end def call case env.stop when :stopped puts "Spring stopped." when :killed $stderr.puts "Spring did not stop; killing forcibly." when :not_running puts "Spring is not running" end end end end end spring-2.1.1/lib/spring/client/version.rb000066400000000000000000000002601372105127500203560ustar00rootroot00000000000000require "spring/version" module Spring module Client class Version < Command def call puts "Spring version #{Spring::VERSION}" end end end end spring-2.1.1/lib/spring/command_wrapper.rb000066400000000000000000000025131372105127500205740ustar00rootroot00000000000000require "spring/configuration" module Spring class CommandWrapper attr_reader :name, :command def initialize(name, command = nil) @name = name @command = command @setup = false end def description if command.respond_to?(:description) command.description else "Runs the #{name} command" end end def setup? @setup end def setup if !setup? && command.respond_to?(:setup) command.setup @setup = true return true else @setup = true return false end end def call if command.respond_to?(:call) command.call else load exec end end def gem_name if command.respond_to?(:gem_name) command.gem_name else exec_name end end def exec_name if command.respond_to?(:exec_name) command.exec_name else name end end def binstub Spring.application_root_path.join(binstub_name) end def binstub_name "bin/#{name}" end def exec if binstub.exist? binstub.to_s else Gem.bin_path(gem_name, exec_name) end end def env(args) if command.respond_to?(:env) command.env(args) end end end end spring-2.1.1/lib/spring/commands.rb000066400000000000000000000022401372105127500172140ustar00rootroot00000000000000require "spring/watcher" require "spring/command_wrapper" module Spring @commands = {} class << self attr_reader :commands end def self.register_command(name, command = nil) commands[name] = CommandWrapper.new(name, command) end def self.command?(name) commands.include? name end def self.command(name) commands.fetch name end require "spring/commands/rails" require "spring/commands/rake" # Load custom commands, if any. # needs to be at the end to allow modification of existing commands. config = File.expand_path("~/.spring.rb") require config if File.exist?(config) # If the config/spring.rb contains requires for commands from other gems, # then we need to be under bundler. require "bundler/setup" # Auto-require any Spring extensions which are in the Gemfile Gem::Specification.map(&:name).grep(/^spring-/).each do |command| begin require command rescue LoadError => error if error.message.include?(command) require command.gsub("-", "/") else raise end end end config = File.expand_path("./config/spring.rb") require config if File.exist?(config) end spring-2.1.1/lib/spring/commands/000077500000000000000000000000001372105127500166715ustar00rootroot00000000000000spring-2.1.1/lib/spring/commands/rails.rb000066400000000000000000000043721372105127500203360ustar00rootroot00000000000000module Spring module Commands class Rails def call ARGV.unshift command_name load Dir.glob(::Rails.root.join("{bin,script}/rails")).first end def description nil end end class RailsConsole < Rails def env(args) return args.first if args.first && !args.first.index("-") environment = nil args.each.with_index do |arg, i| if arg =~ /--environment=(\w+)/ environment = $1 elsif i > 0 && args[i - 1] == "-e" environment = arg end end environment end def command_name "console" end end class RailsGenerate < Rails def command_name "generate" end end class RailsDestroy < Rails def command_name "destroy" end end class RailsRunner < Rails def call ARGV.replace extract_environment(ARGV).first super end def env(args) extract_environment(args).last end def command_name "runner" end def extract_environment(args) environment = nil args = args.select.with_index { |arg, i| case arg when "-e" false when /--environment=(\w+)/ environment = $1 false else if i > 0 && args[i - 1] == "-e" environment = arg false else true end end } [args, environment] end end class RailsTest < Rails def env(args) environment = "test" args.each.with_index do |arg, i| if arg =~ /--environment=(\w+)/ environment = $1 elsif i > 0 && args[i - 1] == "-e" environment = arg end end environment end def command_name "test" end end Spring.register_command "rails_console", RailsConsole.new Spring.register_command "rails_generate", RailsGenerate.new Spring.register_command "rails_destroy", RailsDestroy.new Spring.register_command "rails_runner", RailsRunner.new Spring.register_command "rails_test", RailsTest.new end end spring-2.1.1/lib/spring/commands/rake.rb000066400000000000000000000014361372105127500201440ustar00rootroot00000000000000module Spring module Commands class Rake class << self attr_accessor :environment_matchers end self.environment_matchers = { :default => "test", /^test($|:)/ => "test" } def env(args) # This is an adaption of the matching that Rake itself does. # See: https://github.com/jimweirich/rake/blob/3754a7639b3f42c2347857a0878beb3523542aee/lib/rake/application.rb#L691-L692 if env = args.grep(/^(RAILS|RACK)_ENV=(.*)$/m).last return env.split("=").last end self.class.environment_matchers.each do |matcher, environment| return environment if matcher === (args.first || :default) end nil end end Spring.register_command "rake", Rake.new end end spring-2.1.1/lib/spring/configuration.rb000066400000000000000000000023541372105127500202700ustar00rootroot00000000000000require "spring/errors" module Spring class << self attr_accessor :application_root, :quiet def gemfile if /\s1.9.[0-9]/ === Bundler.ruby_scope.gsub(/[\/\s]+/,'') ENV["BUNDLE_GEMFILE"] || "Gemfile" else Bundler.default_gemfile end end def after_fork_callbacks @after_fork_callbacks ||= [] end def after_fork(&block) after_fork_callbacks << block end def verify_environment application_root_path end def application_root_path @application_root_path ||= begin if application_root path = Pathname.new(File.expand_path(application_root)) else path = project_root_path end raise MissingApplication.new(path) unless path.join("config/application.rb").exist? path end end def project_root_path @project_root_path ||= find_project_root(Pathname.new(File.expand_path(Dir.pwd))) end private def find_project_root(current_dir) if current_dir.join(gemfile).exist? current_dir elsif current_dir.root? raise UnknownProject.new(Dir.pwd) else find_project_root(current_dir.parent) end end end self.quiet = false end spring-2.1.1/lib/spring/env.rb000066400000000000000000000047601372105127500162140ustar00rootroot00000000000000require "pathname" require "fileutils" require "digest/md5" require "tmpdir" require "spring/version" require "spring/sid" require "spring/configuration" module Spring IGNORE_SIGNALS = %w(INT QUIT) STOP_TIMEOUT = 2 # seconds class Env attr_reader :log_file def initialize(options = {}) @root = options[:root] @project_root = options[:root] @log_file = options[:log_file] || File.open(ENV["SPRING_LOG"] || File::NULL, "a") end def root @root ||= Spring.application_root_path end def project_root @project_root ||= Spring.project_root_path end def version Spring::VERSION end def tmp_path path = Pathname.new( ENV["SPRING_TMP_PATH"] || File.join(ENV['XDG_RUNTIME_DIR'] || Dir.tmpdir, "spring-#{Process.uid}") ) FileUtils.mkdir_p(path) unless path.exist? path end def application_id ENV["SPRING_APPLICATION_ID"] || Digest::MD5.hexdigest(RUBY_VERSION + project_root.to_s) end def socket_path Pathname.new(ENV["SPRING_SOCKET"] || tmp_path.join(application_id)) end def socket_name socket_path.to_s end def pidfile_path Pathname.new(ENV["SPRING_PIDFILE"] || socket_path.dirname.join("#{socket_path.basename(".*")}.pid")) end def pid pidfile_path.exist? ? pidfile_path.read.to_i : nil rescue Errno::ENOENT # This can happen if the pidfile is removed after we check it # exists end def app_name root.basename end def server_running? pidfile = pidfile_path.open('r+') !pidfile.flock(File::LOCK_EX | File::LOCK_NB) rescue Errno::ENOENT false ensure if pidfile pidfile.flock(File::LOCK_UN) pidfile.close end end def log(message) log_file.puts "[#{Time.now}] [#{Process.pid}] #{message}" log_file.flush end def stop if server_running? timeout = Time.now + STOP_TIMEOUT kill 'TERM' sleep 0.1 until !server_running? || Time.now >= timeout if server_running? kill 'KILL' :killed else :stopped end else :not_running end end def kill(sig) pid = self.pid Process.kill(sig, pid) if pid rescue Errno::ESRCH # already dead end def server_command ENV["SPRING_SERVER_COMMAND"] || "#{File.expand_path("../../../bin/spring", __FILE__)} server --background" end end end spring-2.1.1/lib/spring/errors.rb000066400000000000000000000017551372105127500167410ustar00rootroot00000000000000module Spring class ClientError < StandardError; end class UnknownProject < StandardError attr_reader :current_dir def initialize(current_dir) @current_dir = current_dir end def message "Spring was unable to locate the root of your project. There was no Gemfile " \ "present in the current directory (#{current_dir}) or any of the parent " \ "directories." end end class MissingApplication < ClientError attr_reader :project_root def initialize(project_root) @project_root = project_root end def message "Spring was unable to find your config/application.rb file. " \ "Your project root was detected at #{project_root}, so Spring " \ "looked for #{project_root}/config/application.rb but it doesn't exist. You can " \ "configure the root of your application by setting Spring.application_root in " \ "config/spring.rb." end end class CommandNotFound < ClientError end end spring-2.1.1/lib/spring/failsafe_thread.rb000066400000000000000000000002561372105127500205210ustar00rootroot00000000000000require 'thread' module Spring class << self def failsafe_thread Thread.new { begin yield rescue end } end end end spring-2.1.1/lib/spring/json.rb000066400000000000000000000347301372105127500163750ustar00rootroot00000000000000# encoding: UTF-8 # ### WHY SPRING VENDORS A JSON LIBRARY ### # # Spring is designed to be able to run outside of bundler. Ruby has a # built-in "json" library, which we could use. However if we require # that, and somebody has a different (perhaps newer) version of the # json *gem* in their Gemfile, it will never get used since we already # required the older version of the json library from the stdlib. # Therefore, we are vendoring a json library for our own use in order to # not interfere. module Spring module JSON def self.load(string) string.force_encoding("utf-8") OkJson.decode(string) end def self.dump(data) OkJson.encode(data) end end end # # Copyright 2011, 2012 Keith Rarick # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # See https://github.com/kr/okjson for updates. require 'stringio' # Some parts adapted from # http://golang.org/src/pkg/json/decode.go and # http://golang.org/src/pkg/utf8/utf8.go module Spring module OkJson Upstream = '43' extend self # Decodes a json document in string s and # returns the corresponding ruby value. # String s must be valid UTF-8. If you have # a string in some other encoding, convert # it first. # # String values in the resulting structure # will be UTF-8. def decode(s) ts = lex(s) v, ts = textparse(ts) if ts.length > 0 raise Error, 'trailing garbage' end v end # Encodes x into a json text. It may contain only # Array, Hash, String, Numeric, true, false, nil. # (Note, this list excludes Symbol.) # X itself must be an Array or a Hash. # No other value can be encoded, and an error will # be raised if x contains any other value, such as # Nan, Infinity, Symbol, and Proc, or if a Hash key # is not a String. # Strings contained in x must be valid UTF-8. def encode(x) case x when Hash then objenc(x) when Array then arrenc(x) else raise Error, 'root value must be an Array or a Hash' end end def valenc(x) case x when Hash then objenc(x) when Array then arrenc(x) when String then strenc(x) when Numeric then numenc(x) when true then "true" when false then "false" when nil then "null" else raise Error, "cannot encode #{x.class}: #{x.inspect}" end end private # Parses a "json text" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. # Note: this is almost the same as valparse, # except that it does not accept atomic values. def textparse(ts) if ts.length <= 0 raise Error, 'empty' end typ, _, val = ts[0] case typ when '{' then objparse(ts) when '[' then arrparse(ts) else raise Error, "unexpected #{val.inspect}" end end # Parses a "value" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def valparse(ts) if ts.length <= 0 raise Error, 'empty' end typ, _, val = ts[0] case typ when '{' then objparse(ts) when '[' then arrparse(ts) when :val,:str then [val, ts[1..-1]] else raise Error, "unexpected #{val.inspect}" end end # Parses an "object" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def objparse(ts) ts = eat('{', ts) obj = {} if ts[0][0] == '}' return obj, ts[1..-1] end k, v, ts = pairparse(ts) obj[k] = v if ts[0][0] == '}' return obj, ts[1..-1] end loop do ts = eat(',', ts) k, v, ts = pairparse(ts) obj[k] = v if ts[0][0] == '}' return obj, ts[1..-1] end end end # Parses a "member" in the sense of RFC 4627. # Returns the parsed values and any trailing tokens. def pairparse(ts) (typ, _, k), ts = ts[0], ts[1..-1] if typ != :str raise Error, "unexpected #{k.inspect}" end ts = eat(':', ts) v, ts = valparse(ts) [k, v, ts] end # Parses an "array" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def arrparse(ts) ts = eat('[', ts) arr = [] if ts[0][0] == ']' return arr, ts[1..-1] end v, ts = valparse(ts) arr << v if ts[0][0] == ']' return arr, ts[1..-1] end loop do ts = eat(',', ts) v, ts = valparse(ts) arr << v if ts[0][0] == ']' return arr, ts[1..-1] end end end def eat(typ, ts) if ts[0][0] != typ raise Error, "expected #{typ} (got #{ts[0].inspect})" end ts[1..-1] end # Scans s and returns a list of json tokens, # excluding white space (as defined in RFC 4627). def lex(s) ts = [] while s.length > 0 typ, lexeme, val = tok(s) if typ == nil raise Error, "invalid character at #{s[0,10].inspect}" end if typ != :space ts << [typ, lexeme, val] end s = s[lexeme.length..-1] end ts end # Scans the first token in s and # returns a 3-element list, or nil # if s does not begin with a valid token. # # The first list element is one of # '{', '}', ':', ',', '[', ']', # :val, :str, and :space. # # The second element is the lexeme. # # The third element is the value of the # token for :val and :str, otherwise # it is the lexeme. def tok(s) case s[0] when ?{ then ['{', s[0,1], s[0,1]] when ?} then ['}', s[0,1], s[0,1]] when ?: then [':', s[0,1], s[0,1]] when ?, then [',', s[0,1], s[0,1]] when ?[ then ['[', s[0,1], s[0,1]] when ?] then [']', s[0,1], s[0,1]] when ?n then nulltok(s) when ?t then truetok(s) when ?f then falsetok(s) when ?" then strtok(s) when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]] else numtok(s) end end def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end def numtok(s) m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s) if m && m.begin(0) == 0 if !m[2] && !m[3] [:val, m[0], Integer(m[0])] elsif m[2] [:val, m[0], Float(m[0])] else [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))] end else [] end end def strtok(s) m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s) if ! m raise Error, "invalid string literal at #{abbrev(s)}" end [:str, m[0], unquote(m[0])] end def abbrev(s) t = s[0,10] p = t['`'] t = t[0,p] if p t = t + '...' if t.length < s.length '`' + t + '`' end # Converts a quoted json string literal q into a UTF-8-encoded string. # The rules are different than for Ruby, so we cannot use eval. # Unquote will raise an error if q contains control characters. def unquote(q) q = q[1...-1] a = q.dup # allocate a big enough string # In ruby >= 1.9, a[w] is a codepoint, not a byte. if rubydoesenc? a.force_encoding('UTF-8') end r, w = 0, 0 while r < q.length c = q[r] if c == ?\\ r += 1 if r >= q.length raise Error, "string literal ends with a \"\\\": \"#{q}\"" end case q[r] when ?",?\\,?/,?' a[w] = q[r] r += 1 w += 1 when ?b,?f,?n,?r,?t a[w] = Unesc[q[r]] r += 1 w += 1 when ?u r += 1 uchar = begin hexdec4(q[r,4]) rescue RuntimeError => e raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}" end r += 4 if surrogate? uchar if q.length >= r+6 uchar1 = hexdec4(q[r+2,4]) uchar = subst(uchar, uchar1) if uchar != Ucharerr # A valid pair; consume. r += 6 end end end if rubydoesenc? a[w] = '' << uchar w += 1 else w += ucharenc(a, w, uchar) end else raise Error, "invalid escape char #{q[r]} in \"#{q}\"" end elsif c == ?" || c < Spc raise Error, "invalid character in string literal \"#{q}\"" else # Copy anything else byte-for-byte. # Valid UTF-8 will remain valid UTF-8. # Invalid UTF-8 will remain invalid UTF-8. # In ruby >= 1.9, c is a codepoint, not a byte, # in which case this is still what we want. a[w] = c r += 1 w += 1 end end a[0,w] end # Encodes unicode character u as UTF-8 # bytes in string a at position i. # Returns the number of bytes written. def ucharenc(a, i, u) if u <= Uchar1max a[i] = (u & 0xff).chr 1 elsif u <= Uchar2max a[i+0] = (Utag2 | ((u>>6)&0xff)).chr a[i+1] = (Utagx | (u&Umaskx)).chr 2 elsif u <= Uchar3max a[i+0] = (Utag3 | ((u>>12)&0xff)).chr a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr a[i+2] = (Utagx | (u&Umaskx)).chr 3 else a[i+0] = (Utag4 | ((u>>18)&0xff)).chr a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr a[i+3] = (Utagx | (u&Umaskx)).chr 4 end end def hexdec4(s) if s.length != 4 raise Error, 'short' end (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3]) end def subst(u1, u2) if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3 return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself end return Ucharerr end def surrogate?(u) Usurr1 <= u && u < Usurr3 end def nibble(c) if ?0 <= c && c <= ?9 then c.ord - ?0.ord elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10 elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10 else raise Error, "invalid hex code #{c}" end end def objenc(x) '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}' end def arrenc(a) '[' + a.map{|x| valenc(x)}.join(',') + ']' end def keyenc(k) case k when String then strenc(k) else raise Error, "Hash key is not a string: #{k.inspect}" end end def strenc(s) t = StringIO.new t.putc(?") r = 0 while r < s.length case s[r] when ?" then t.print('\\"') when ?\\ then t.print('\\\\') when ?\b then t.print('\\b') when ?\f then t.print('\\f') when ?\n then t.print('\\n') when ?\r then t.print('\\r') when ?\t then t.print('\\t') else c = s[r] # In ruby >= 1.9, s[r] is a codepoint, not a byte. if rubydoesenc? begin # c.ord will raise an error if c is invalid UTF-8 if c.ord < Spc.ord c = "\\u%04x" % [c.ord] end t.write(c) rescue t.write(Ustrerr) end elsif c < Spc t.write("\\u%04x" % c) elsif Spc <= c && c <= ?~ t.putc(c) else n = ucharcopy(t, s, r) # ensure valid UTF-8 output r += n - 1 # r is incremented below end end r += 1 end t.putc(?") t.string end def numenc(x) if ((x.nan? || x.infinite?) rescue false) raise Error, "Numeric cannot be represented: #{x}" end "#{x}" end # Copies the valid UTF-8 bytes of a single character # from string s at position i to I/O object t, and # returns the number of bytes copied. # If no valid UTF-8 char exists at position i, # ucharcopy writes Ustrerr and returns 1. def ucharcopy(t, s, i) n = s.length - i raise Utf8Error if n < 1 c0 = s[i].ord # 1-byte, 7-bit sequence? if c0 < Utagx t.putc(c0) return 1 end raise Utf8Error if c0 < Utag2 # unexpected continuation byte? raise Utf8Error if n < 2 # need continuation byte c1 = s[i+1].ord raise Utf8Error if c1 < Utagx || Utag2 <= c1 # 2-byte, 11-bit sequence? if c0 < Utag3 raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max t.putc(c0) t.putc(c1) return 2 end # need second continuation byte raise Utf8Error if n < 3 c2 = s[i+2].ord raise Utf8Error if c2 < Utagx || Utag2 <= c2 # 3-byte, 16-bit sequence? if c0 < Utag4 u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx) raise Utf8Error if u <= Uchar2max t.putc(c0) t.putc(c1) t.putc(c2) return 3 end # need third continuation byte raise Utf8Error if n < 4 c3 = s[i+3].ord raise Utf8Error if c3 < Utagx || Utag2 <= c3 # 4-byte, 21-bit sequence? if c0 < Utag5 u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx) raise Utf8Error if u <= Uchar3max t.putc(c0) t.putc(c1) t.putc(c2) t.putc(c3) return 4 end raise Utf8Error rescue Utf8Error t.write(Ustrerr) return 1 end def rubydoesenc? ::String.method_defined?(:force_encoding) end class Utf8Error < ::StandardError end class Error < ::StandardError end Utagx = 0b1000_0000 Utag2 = 0b1100_0000 Utag3 = 0b1110_0000 Utag4 = 0b1111_0000 Utag5 = 0b1111_1000 Umaskx = 0b0011_1111 Umask2 = 0b0001_1111 Umask3 = 0b0000_1111 Umask4 = 0b0000_0111 Uchar1max = (1<<7) - 1 Uchar2max = (1<<11) - 1 Uchar3max = (1<<16) - 1 Ucharerr = 0xFFFD # unicode "replacement char" Ustrerr = "\xef\xbf\xbd" # unicode "replacement char" Usurrself = 0x10000 Usurr1 = 0xd800 Usurr2 = 0xdc00 Usurr3 = 0xe000 Spc = ' '[0] Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t} end end spring-2.1.1/lib/spring/process_title_updater.rb000066400000000000000000000023351372105127500220230ustar00rootroot00000000000000module Spring # Yes, I know this reimplements a bunch of stuff in Active Support, but # I don't want the Spring client to depend on AS, in order to keep its # load time down. class ProcessTitleUpdater SECOND = 1 MINUTE = 60 HOUR = 60*60 def self.run(&block) updater = new(&block) Spring.failsafe_thread { $0 = updater.value loop { $0 = updater.next } } end attr_reader :block def initialize(start = Time.now, &block) @start = start @block = block end def interval distance = Time.now - @start if distance < MINUTE SECOND elsif distance < HOUR MINUTE else HOUR end end def next sleep interval value end def value block.call(distance_in_words) end def distance_in_words(now = Time.now) distance = now - @start if distance < MINUTE pluralize(distance, "sec") elsif distance < HOUR pluralize(distance / MINUTE, "min") else pluralize(distance / HOUR, "hour") end end private def pluralize(amount, unit) "#{amount.to_i} #{amount.to_i == 1 ? unit : "#{unit}s"}" end end end spring-2.1.1/lib/spring/server.rb000066400000000000000000000072771372105127500167400ustar00rootroot00000000000000module Spring ORIGINAL_ENV = ENV.to_hash end require "spring/boot" require "spring/application_manager" # Must be last, as it requires bundler/setup, which alters the load path require "spring/commands" module Spring class Server def self.boot(options = {}) new(options).boot end attr_reader :env def initialize(options = {}) @foreground = options.fetch(:foreground, false) @env = options[:env] || default_env @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k, env) } @pidfile = env.pidfile_path.open('a') @mutex = Mutex.new end def foreground? @foreground end def log(message) env.log "[server] #{message}" end def boot Spring.verify_environment write_pidfile set_pgid unless foreground? ignore_signals unless foreground? set_exit_hook set_process_title start_server end def start_server server = UNIXServer.open(env.socket_name) log "started on #{env.socket_name}" loop { serve server.accept } rescue Interrupt end def serve(client) log "accepted client" client.puts env.version app_client = client.recv_io command = JSON.load(client.read(client.gets.to_i)) args, default_rails_env = command.values_at('args', 'default_rails_env') if Spring.command?(args.first) log "running command #{args.first}" client.puts client.puts @applications[rails_env_for(args, default_rails_env)].run(app_client) else log "command not found #{args.first}" client.close end rescue SocketError => e raise e unless client.eof? ensure redirect_output end def rails_env_for(args, default_rails_env) Spring.command(args.first).env(args.drop(1)) || default_rails_env end # Boot the server into the process group of the current session. # This will cause it to be automatically killed once the session # ends (i.e. when the user closes their terminal). def set_pgid Process.setpgid(0, SID.pgid) end # Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line # will kill the server/application. def ignore_signals IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") } end def set_exit_hook server_pid = Process.pid # We don't want this hook to run in any forks of the current process at_exit { shutdown if Process.pid == server_pid } end def shutdown log "shutting down" [env.socket_path, env.pidfile_path].each do |path| if path.exist? path.unlink rescue nil end end @applications.values.map { |a| Spring.failsafe_thread { a.stop } }.map(&:join) end def write_pidfile if @pidfile.flock(File::LOCK_EX | File::LOCK_NB) @pidfile.truncate(0) @pidfile.write("#{Process.pid}\n") @pidfile.fsync else exit 1 end end # We need to redirect STDOUT and STDERR, otherwise the server will # keep the original FDs open which would break piping. (e.g. # `spring rake -T | grep db` would hang forever because the server # would keep the stdout FD open.) def redirect_output [STDOUT, STDERR].each { |stream| stream.reopen(env.log_file) } end def set_process_title ProcessTitleUpdater.run { |distance| "spring server | #{env.app_name} | started #{distance} ago" } end private def default_env Env.new(log_file: default_log_file) end def default_log_file if foreground? && !ENV["SPRING_LOG"] $stdout else nil end end end end spring-2.1.1/lib/spring/sid.rb000066400000000000000000000017411372105127500161770ustar00rootroot00000000000000begin # If rubygems is present, keep it out of the way when loading fiddle, # otherwise if fiddle is not installed then rubygems will load all # gemspecs in its (futile) search for fiddle, which is slow. if respond_to?(:gem_original_require, true) gem_original_require 'fiddle' else require 'fiddle' end rescue LoadError end module Spring module SID def self.fiddle_func @fiddle_func ||= Fiddle::Function.new( DL::Handle::DEFAULT['getsid'], [Fiddle::TYPE_INT], Fiddle::TYPE_INT ) end def self.sid @sid ||= begin if Process.respond_to?(:getsid) # Ruby 2 Process.getsid elsif defined?(Fiddle) and defined?(DL) # Ruby 1.9.3 compiled with libffi support fiddle_func.call(0) else # last resort: shell out `ps -p #{Process.pid} -o sess=`.to_i end end end def self.pgid Process.getpgid(sid) end end end spring-2.1.1/lib/spring/version.rb000066400000000000000000000000461372105127500171020ustar00rootroot00000000000000module Spring VERSION = "2.1.1" end spring-2.1.1/lib/spring/watcher.rb000066400000000000000000000012221372105127500170470ustar00rootroot00000000000000require "spring/watcher/abstract" require "spring/configuration" module Spring class << self attr_accessor :watch_interval attr_writer :watcher attr_reader :watch_method end def self.watch_method=(method) if method.is_a?(Class) @watch_method = method else require "spring/watcher/#{method}" @watch_method = Watcher.const_get(method.to_s.gsub(/(^.|_.)/) { $1[-1].upcase }) end end self.watch_interval = 0.2 self.watch_method = :polling def self.watcher @watcher ||= watch_method.new(Spring.application_root_path, watch_interval) end def self.watch(*items) watcher.add(*items) end end spring-2.1.1/lib/spring/watcher/000077500000000000000000000000001372105127500165255ustar00rootroot00000000000000spring-2.1.1/lib/spring/watcher/abstract.rb000066400000000000000000000052031372105127500206550ustar00rootroot00000000000000require "set" require "pathname" require "mutex_m" module Spring module Watcher # A user of a watcher can use IO.select to wait for changes: # # watcher = MyWatcher.new(root, latency) # IO.select([watcher]) # watcher is running in background # watcher.stale? # => true class Abstract include Mutex_m attr_reader :files, :directories, :root, :latency def initialize(root, latency) super() @root = File.realpath(root) @latency = latency @files = Set.new @directories = Set.new @stale = false @listeners = [] @on_debug = nil end def on_debug(&block) @on_debug = block end def debug @on_debug.call(yield) if @on_debug end def add(*items) debug { "watcher: add: #{items.inspect}" } items = items.flatten.map do |item| item = Pathname.new(item) if item.relative? Pathname.new("#{root}/#{item}") else item end end items = items.select do |item| if item.symlink? item.readlink.exist?.tap do |exists| if !exists debug { "add: ignoring dangling symlink: #{item.inspect} -> #{item.readlink.inspect}" } end end else item.exist? end end synchronize { items.each do |item| if item.directory? directories << item.realpath.to_s else begin files << item.realpath.to_s rescue Errno::ENOENT # Race condition. Ignore symlinks whose target was removed # since the check above, or are deeply chained. debug { "add: ignoring now-dangling symlink: #{item.inspect} -> #{item.readlink.inspect}" } end end end subjects_changed } end def stale? @stale end def on_stale(&block) debug { "added listener: #{block.inspect}" } @listeners << block end def mark_stale return if stale? @stale = true debug { "marked stale, calling listeners: listeners=#{@listeners.inspect}" } @listeners.each(&:call) end def restart debug { "restarting" } stop start end def start raise NotImplementedError end def stop raise NotImplementedError end def subjects_changed raise NotImplementedError end end end end spring-2.1.1/lib/spring/watcher/polling.rb000066400000000000000000000043171372105127500205230ustar00rootroot00000000000000require "spring/watcher/abstract" module Spring module Watcher class Polling < Abstract attr_reader :mtime def initialize(root, latency) super @mtime = 0 @poller = nil end def check_stale synchronize do computed = compute_mtime if mtime < computed debug { "check_stale: mtime=#{mtime.inspect} < computed=#{computed.inspect}" } mark_stale end end end def add(*) check_stale if @poller super end def start debug { "start: poller=#{@poller.inspect}" } unless @poller @poller = Thread.new { Thread.current.abort_on_exception = true begin until stale? Kernel.sleep latency check_stale end rescue Exception => e debug do "poller: aborted: #{e.class}: #{e}\n #{e.backtrace.join("\n ")}" end raise ensure @poller = nil end } end end def stop debug { "stopping poller: #{@poller.inspect}" } if @poller @poller.kill @poller = nil end end def running? @poller && @poller.alive? end def subjects_changed computed = compute_mtime debug { "subjects_changed: mtime #{@mtime} -> #{computed}" } @mtime = computed end private def compute_mtime expanded_files.map do |f| # Get the mtime of symlink targets. Ignore dangling symlinks. if File.symlink?(f) begin File.mtime(f) rescue Errno::ENOENT 0 end # If a file no longer exists, treat it as changed. else begin File.mtime(f) rescue Errno::ENOENT debug { "compute_mtime: no longer exists: #{f}" } Float::MAX end end.to_f end.max || 0 end def expanded_files files + Dir["{#{directories.map { |d| "#{d}/**/*" }.join(",")}}"] end end end end spring-2.1.1/spring.gemspec000066400000000000000000000014211372105127500156650ustar00rootroot00000000000000require './lib/spring/version' Gem::Specification.new do |gem| gem.name = "spring" gem.version = Spring::VERSION gem.authors = ["Jon Leighton"] gem.email = ["j@jonathanleighton.com"] gem.summary = "Rails application preloader" gem.description = "Preloads your application so things like console, rake and tests run faster" gem.homepage = "https://github.com/rails/spring" gem.license = "MIT" gem.files = Dir["LICENSE.txt", "README.md", "lib/**/*", "bin/*"] gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.required_ruby_version = ">= 2.4.0" gem.add_development_dependency 'rake' gem.add_development_dependency 'bump' gem.add_development_dependency 'activesupport' end spring-2.1.1/test/000077500000000000000000000000001372105127500137775ustar00rootroot00000000000000spring-2.1.1/test/acceptance_test.rb000066400000000000000000000001231372105127500174450ustar00rootroot00000000000000require_relative "helper" class AcceptanceTest < Spring::Test::AcceptanceTest end spring-2.1.1/test/apps/000077500000000000000000000000001372105127500147425ustar00rootroot00000000000000spring-2.1.1/test/apps/.gitignore000066400000000000000000000000331372105127500167260ustar00rootroot00000000000000* !template.rb !.gitignore spring-2.1.1/test/helper.rb000066400000000000000000000002111372105127500155750ustar00rootroot00000000000000require "bundler/setup" require "minitest/autorun" require_relative "support/test" Spring::Test.root = File.expand_path('..', __FILE__) spring-2.1.1/test/support/000077500000000000000000000000001372105127500155135ustar00rootroot00000000000000spring-2.1.1/test/support/acceptance_test.rb000066400000000000000000000523011372105127500211660ustar00rootroot00000000000000require "io/wait" require "timeout" require "spring/sid" require "spring/client" require "active_support/core_ext/string/strip" module Spring module Test class AcceptanceTest < ActiveSupport::TestCase runnables.delete self # prevent Minitest running this class DEFAULT_SPEEDUP = 0.8 def rails_version ENV['RAILS_VERSION'] || '~> 5.0.0' end # Extension point for spring-watchers-listen def generator_klass Spring::Test::ApplicationGenerator end def generator @@generator ||= generator_klass.new(rails_version) end def app @app ||= Spring::Test::Application.new("#{Spring::Test.root}/apps/tmp") end def spring_env app.spring_env end def assert_output(artifacts, expected) expected.each do |stream, output| assert_match output, artifacts[stream], "expected #{stream} to include #{output.inspect}.\n\n#{app.debug(artifacts)}" end end def assert_success(command, expected_output = nil) artifacts = app.run(*Array(command)) assert artifacts[:status].success?, "expected successful exit status\n\n#{app.debug(artifacts)}" assert_output artifacts, expected_output if expected_output end def assert_failure(command, expected_output = nil) artifacts = app.run(*Array(command)) assert !artifacts[:status].success?, "expected unsuccessful exit status\n\n#{app.debug(artifacts)}" assert_output artifacts, expected_output if expected_output end def refute_output_includes(command, not_expected) artifacts = app.run(*Array(command)) not_expected.each do |stream, output| assert !artifacts[stream].include?(output), "expected #{stream} to not include '#{output}'.\n\n#{app.debug(artifacts)}" end end def assert_speedup(ratio = DEFAULT_SPEEDUP) if ENV['CI'] yield else app.with_timing do yield assert app.timing_ratio < ratio, "#{app.last_time} was not less than #{ratio} of #{app.first_time}" end end end def without_gem(name) gem_home = app.gem_home.join('gems') FileUtils.mv(gem_home.join(name), app.root) yield ensure FileUtils.mv(app.root.join(name), gem_home) end setup do generator.generate_if_missing generator.install_spring generator.copy_to(app.root) end teardown do app.stop_spring end test "basic" do assert_speedup do 2.times { app.run app.spring_test_command } end end test "help message when called without arguments" do assert_success "bin/spring", stdout: 'Usage: spring COMMAND [ARGS]' assert spring_env.server_running? end test "shows help" do assert_success "bin/spring help", stdout: 'Usage: spring COMMAND [ARGS]' assert_success "bin/spring -h", stdout: 'Usage: spring COMMAND [ARGS]' assert_success "bin/spring --help", stdout: 'Usage: spring COMMAND [ARGS]' refute spring_env.server_running? end test "tells the user that Spring is being used when used automatically via binstubs" do assert_success "bin/rails runner ''", stderr: "Running via Spring preloader in process" assert_success app.spring_test_command, stderr: "Running via Spring preloader in process" end test "does not tell the user that Spring is being used when used automatically via binstubs but quiet is enabled" do File.write("#{app.user_home}/.spring.rb", "Spring.quiet = true") assert_success "bin/rails runner ''" refute_output_includes "bin/rails runner ''", stderr: 'Running via Spring preloader in process' end test "test changes are picked up" do assert_speedup do assert_success app.spring_test_command, stdout: "0 failures" app.insert_into_test "raise 'omg'" assert_failure app.spring_test_command, stdout: "RuntimeError: omg" end end test "code changes are picked up" do assert_speedup do assert_success app.spring_test_command, stdout: "0 failures" File.write(app.controller, app.controller.read.sub("@posts = Post.all", "raise 'omg'")) assert_failure app.spring_test_command, stdout: "RuntimeError: omg" end end test "code changes in pre-referenced app files are picked up" do File.write(app.path("config/initializers/load_posts_controller.rb"), "PostsController\n") assert_speedup do assert_success app.spring_test_command, stdout: "0 failures" File.write(app.controller, app.controller.read.sub("@posts = Post.all", "raise 'omg'")) assert_failure app.spring_test_command, stdout: "RuntimeError: omg" end end test "app gets reloaded when preloaded files change" do assert_success app.spring_test_command File.write(app.application_config, app.application_config.read + <<-RUBY.strip_heredoc) class Foo def self.omg raise "omg" end end RUBY app.insert_into_test "Foo.omg" app.await_reload assert_failure app.spring_test_command, stdout: "RuntimeError: omg" end test "app gets reloaded even with a ton of boot output" do limit = UNIXSocket.pair.first.getsockopt(:SOCKET, :SNDBUF).int assert_success app.spring_test_command File.write(app.path("config/initializers/verbose.rb"), "#{limit}.times { puts 'x' }") app.await_reload assert_success app.spring_test_command end test "app gets reloaded even with abort_on_exception=true" do assert_success app.spring_test_command File.write(app.path("config/initializers/thread_config.rb"), "Thread.abort_on_exception = true") app.await_reload assert_success app.spring_test_command end test "app recovers when a boot-level error is introduced" do config = app.application_config.read assert_success app.spring_test_command File.write(app.application_config, "#{config}\nomg") app.await_reload assert_failure app.spring_test_command File.write(app.application_config, config) assert_success app.spring_test_command end test "stop command kills server" do app.run app.spring_test_command assert spring_env.server_running?, "The server should be running but it isn't" assert_success "bin/spring stop" assert !spring_env.server_running?, "The server should not be running but it is" end test "custom commands" do # Start spring before setting up the command, to test that it gracefully upgrades itself assert_success "bin/rails runner ''" File.write(app.spring_config, <<-RUBY.strip_heredoc) class CustomCommand def call puts "omg" end def exec_name "rake" end end Spring.register_command "custom", CustomCommand.new RUBY assert_success "bin/spring custom", stdout: "omg" assert_success "bin/spring binstub custom" assert_success "bin/custom", stdout: "omg" app.env["DISABLE_SPRING"] = "1" assert_success %{bin/custom -e 'puts "foo"'}, stdout: "foo" end test "binstub" do assert_success "bin/rails server --help", stdout: /Usage:\s+rails server/ # rails command fallback assert_success "#{app.spring} binstub rake", stdout: "bin/rake: Spring already present" assert_success "#{app.spring} binstub --remove rake", stdout: "bin/rake: Spring removed" assert !app.path("bin/rake").read.include?(Spring::Client::Binstub::LOADER) assert_success "bin/rake -T", stdout: "rake db:migrate" assert_success "#{app.spring} binstub rake", stdout: "bin/rake: Spring inserted" assert app.path("bin/rake").read.include?(Spring::Client::Binstub::LOADER) end test "binstub remove all" do assert_success "bin/spring binstub --remove --all" refute File.exist?(app.path("bin/spring")) end test "binstub when spring gem is missing" do without_gem "spring-#{Spring::VERSION}" do File.write(app.gemfile, app.gemfile.read.gsub(/gem 'spring.*/, "")) assert_success "bin/rake -T", stdout: "rake db:migrate" end end test "binstub when spring binary is missing" do begin File.rename(app.path("bin/spring"), app.path("bin/spring.bak")) assert_success "bin/rake -T", stdout: "rake db:migrate" ensure File.rename(app.path("bin/spring.bak"), app.path("bin/spring")) end end test "binstub preserve magic comments" do File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby # frozen_string_literal: true # # more comments require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_success "bin/spring binstub rake" expected = <<-RUBY.gsub(/^ /, "") #!/usr/bin/env ruby # frozen_string_literal: true # # more comments #{Spring::Client::Binstub::LOADER.strip} require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_equal expected, app.path("bin/rake").read end test "binstub upgrade with old binstub" do File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty? exec "bundle", "exec", "rake", *ARGV else ARGV.unshift "rake" load Gem.bin_path("spring", "spring") end RUBY File.write(app.path("bin/rails"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty? APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' else ARGV.unshift "rails" load Gem.bin_path("spring", "spring") end RUBY assert_success "bin/spring binstub --all", stdout: "upgraded" expected = <<-RUBY.gsub(/^ /, "") #!/usr/bin/env ruby #{Spring::Client::Binstub::LOADER.strip} require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_equal expected, app.path("bin/rake").read expected = <<-RUBY.gsub(/^ /, "") #!/usr/bin/env ruby #{Spring::Client::Binstub::LOADER.strip} APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' RUBY assert_equal expected, app.path("bin/rails").read end test "binstub upgrade with new binstub variations" do expected = <<-RUBY.gsub(/^ /, "") #!/usr/bin/env ruby #{Spring::Client::Binstub::LOADER.strip} require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY # older variation with double quotes File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby begin load File.expand_path("../spring", __FILE__) rescue LoadError end require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_success "bin/spring binstub rake", stdout: "bin/rake: upgraded" assert_equal expected, app.path("bin/rake").read # newer variation with single quotes File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby begin load File.expand_path('../spring', __FILE__) rescue LoadError end require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_success "bin/spring binstub rake", stdout: "bin/rake: upgraded" assert_equal expected, app.path("bin/rake").read # newer variation which checks end of exception message File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby begin spring_bin_path = File.expand_path('../spring', __FILE__) load spring_bin_path rescue LoadError => e raise unless e.message.end_with? spring_bin_path, 'spring/binstub' end require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_success "bin/spring binstub rake", stdout: "bin/rake: upgraded" assert_equal expected, app.path("bin/rake").read end test "binstub remove with new binstub variations" do # older variation with double quotes File.write(app.path("bin/rake"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby begin load File.expand_path("../spring", __FILE__) rescue LoadError end require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY # newer variation with single quotes File.write(app.path("bin/rails"), <<-RUBY.strip_heredoc) #!/usr/bin/env ruby begin load File.expand_path('../spring', __FILE__) rescue LoadError end APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' RUBY assert_success "bin/spring binstub --remove rake", stdout: "bin/rake: Spring removed" assert_success "bin/spring binstub --remove rails", stdout: "bin/rails: Spring removed" expected = <<-RUBY.strip_heredoc #!/usr/bin/env ruby require 'bundler/setup' load Gem.bin_path('rake', 'rake') RUBY assert_equal expected, app.path("bin/rake").read expected = <<-RUBY.strip_heredoc #!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' RUBY assert_equal expected, app.path("bin/rails").read end test "after fork callback" do File.write(app.spring_config, "Spring.after_fork { puts '!callback!' }") assert_success "bin/rails runner 'puts 2'", stdout: "!callback!\n2" end test "global config file evaluated" do File.write("#{app.user_home}/.spring.rb", "Spring.after_fork { puts '!callback!' }") assert_success "bin/rails runner 'puts 2'", stdout: "!callback!\n2" end test "can define client tasks" do File.write("#{app.spring_client_config}", <<-RUBY) Spring::Client::COMMANDS["foo"] = lambda { |args| puts "bar -- \#{args.inspect}" } RUBY assert_success "bin/spring foo --baz", stdout: "bar -- [\"foo\", \"--baz\"]\n" end test "missing config/application.rb" do app.application_config.delete assert_failure "bin/rake -T", stderr: "unable to find your config/application.rb" end test "piping with boot-level error" do config = app.application_config.read File.write(app.application_config, "#{config}\nomg") assert_success "bin/rake -T | cat" end test "piping" do assert_success "bin/rake -T | grep db", stdout: "rake db:migrate" end test "status" do assert_success "bin/spring status", stdout: "Spring is not running" assert_success "bin/rails runner ''" assert_success "bin/spring status", stdout: "Spring is running" end test "runner command sets Rails environment from command-line options" do assert_success "bin/rails runner -e test 'puts Rails.env'", stdout: "test" assert_success "bin/rails runner --environment=test 'puts Rails.env'", stdout: "test" end test "forcing rails env via environment variable" do app.env['RAILS_ENV'] = 'test' assert_success "bin/rake -p 'Rails.env'", stdout: "test" end test "setting env vars with rake" do File.write(app.path("lib/tasks/env.rake"), <<-RUBY.strip_heredoc) task :print_rails_env => :environment do puts Rails.env end task :print_env do ENV.each { |k, v| puts "\#{k}=\#{v}" } end task(:default).clear.enhance [:print_rails_env] RUBY assert_success "bin/rake RAILS_ENV=test print_rails_env", stdout: "test" assert_success "bin/rake FOO=bar print_env", stdout: "FOO=bar" assert_success "bin/rake", stdout: "test" end test "changing the Gemfile works" do assert_success %(bin/rails runner 'require "sqlite3"') File.write(app.gemfile, app.gemfile.read.gsub(%{gem 'sqlite3'}, %{# gem 'sqlite3'})) app.await_reload assert_failure %(bin/rails runner 'require "sqlite3"'), stderr: "sqlite3" end if RUBY_VERSION >= "2.0.0" test "changing the gems.rb works" do FileUtils.mv(app.gemfile, app.gems_rb) FileUtils.mv(app.gemfile_lock, app.gems_locked) assert_success %(bin/rails runner 'require "sqlite3"') File.write(app.gems_rb, app.gems_rb.read.sub(%{gem 'sqlite3'}, %{# gem 'sqlite3'})) app.await_reload assert_failure %(bin/rails runner 'require "sqlite3"'), stderr: "sqlite3" end end test "changing the Gemfile works when Spring calls into itself" do File.write(app.path("script.rb"), <<-RUBY.strip_heredoc) gemfile = Rails.root.join("Gemfile") File.write(gemfile, "\#{gemfile.read}gem 'text'\\n") Bundler.with_clean_env do system(#{app.env.inspect}, "bundle install") end output = `\#{Rails.root.join('bin/rails')} runner 'require "text"; puts "done";'` exit output.include? "done\n" RUBY assert_success [%(bin/rails runner 'load Rails.root.join("script.rb")'), timeout: 60] end if RUBY_VERSION >= "2.0.0" test "changing the gems.rb works when spring calls into itself" do FileUtils.mv(app.gemfile, app.gems_rb) FileUtils.mv(app.gemfile_lock, app.gems_locked) File.write(app.path("script.rb"), <<-RUBY.strip_heredoc) gemfile = Rails.root.join("gems.rb") File.write(gemfile, "\#{gemfile.read}gem 'text'\\n") Bundler.with_clean_env do system(#{app.env.inspect}, "bundle install") end output = `\#{Rails.root.join('bin/rails')} runner 'require "text"; puts "done";'` exit output.include? "done\n" RUBY assert_success [%(bin/rails runner 'load Rails.root.join("script.rb")'), timeout: 60] end end test "changing the environment between runs" do File.write(app.application_config, "#{app.application_config.read}\nENV['BAR'] = 'bar'") app.env["OMG"] = "1" app.env["FOO"] = "1" app.env["RUBYOPT"] = "-rrubygems" assert_success %(bin/rails runner 'p ENV["OMG"]'), stdout: "1" assert_success %(bin/rails runner 'p ENV["BAR"]'), stdout: "bar" assert_success %(bin/rails runner 'p ENV.key?("BUNDLE_GEMFILE")'), stdout: "true" assert_success %(bin/rails runner 'p ENV["RUBYOPT"]'), stdout: "bundler" app.env["OMG"] = "2" app.env.delete "FOO" assert_success %(bin/rails runner 'p ENV["OMG"]'), stdout: "2" assert_success %(bin/rails runner 'p ENV.key?("FOO")'), stdout: "false" end test "Kernel.raise remains private" do expr = "p Kernel.private_instance_methods.include?(:raise)" assert_success %(bin/rails runner '#{expr}'), stdout: "true" end test "custom bundle path" do bundle_path = app.path(".bundle/#{Bundler.ruby_scope}") bundle_path.dirname.mkpath FileUtils.cp_r "#{app.gem_home}/", bundle_path.to_s app.run! "bundle install --path .bundle --clean --local" assert_speedup do 2.times { assert_success "bundle exec rails runner ''" } end end test "booting a foreground server" do FileUtils.cd(app.root) do assert !spring_env.server_running? assert_success "bin/spring server &" Timeout.timeout(10) do sleep 0.1 until spring_env.server_running? && spring_env.socket_path.exist? end assert_success app.spring_test_command end end test "server boot timeout" do app.env["SPRING_SERVER_COMMAND"] = "sleep 1" File.write("#{app.spring_client_config}", %( Spring::Client::Run.const_set(:BOOT_TIMEOUT, 0.1) )) assert_failure "bin/rails runner ''", stderr: "timed out" end test "no warnings are shown for unsprung commands" do app.env["DISABLE_SPRING"] = "1" refute_output_includes "bin/rails runner ''", stderr: "WARN" end end end end spring-2.1.1/test/support/application.rb000066400000000000000000000124431372105127500203470ustar00rootroot00000000000000require "spring/env" module Spring module Test class Application DEFAULT_TIMEOUT = ENV['CI'] ? 300 : 10 attr_reader :root, :spring_env def initialize(root) @root = Pathname.new(root) @spring_env = Spring::Env.new(root: root) @times = nil end def exists? root.exist? end def stdout @stdout ||= IO.pipe end def stderr @stderr ||= IO.pipe end def log_file @log_file ||= path("tmp/spring.log").open("w+") end def env @env ||= { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => gem_home.to_s, "HOME" => user_home.to_s, "RAILS_ENV" => nil, "RACK_ENV" => nil, "SPRING_LOG" => log_file.path } end def path(addition) root.join addition end def gemfile path "Gemfile" end def gemfile_lock path "Gemfile.lock" end def gems_rb path "gems.rb" end def gems_locked path "gems.locked" end def gem_home path "../gems/#{RUBY_VERSION}" end def user_home path "user_home" end def spring gem_home.join "bin/spring" end def rails_version @rails_version ||= RailsVersion.new(gemfile.read.match(/gem 'rails', '(.*)'/)[1]) end def spring_test_command "bin/rake test #{test}" end def stop_spring run "#{spring} stop" rescue Errno::ENOENT end def test path "test/controllers/posts_controller_test.rb" end def controller path "app/controllers/posts_controller.rb" end def application_config path "config/application.rb" end def spring_config path "config/spring.rb" end def spring_client_config path "config/spring_client.rb" end def run(command, opts = {}) start_time = Time.now Bundler.with_clean_env do Process.spawn( env, command.to_s, out: stdout.last, err: stderr.last, in: :close, chdir: root.to_s, ) end _, status = Timeout.timeout(opts.fetch(:timeout, DEFAULT_TIMEOUT)) { Process.wait2 } if pid = spring_env.pid @server_pid = pid lines = `ps -A -o ppid= -o pid= | egrep '^\\s*#{@server_pid}'`.lines @application_pids = lines.map { |l| l.split.last.to_i } end output = read_streams puts dump_streams(command, output) if ENV["SPRING_DEBUG"] @times << (Time.now - start_time) if @times output.merge(status: status, command: command) rescue Timeout::Error raise Timeout::Error, "While running command:\n\n#{dump_streams(command, read_streams)}" end def with_timing @times = [] yield ensure @times = nil end def last_time @times.last end def first_time @times.first end def timing_ratio last_time / first_time end def read_streams { stdout: read_stream(stdout.first), stderr: read_stream(stderr.first), log: read_stream(log_file) } end def read_stream(stream) output = "" while IO.select([stream], [], [], 0.5) && !stream.eof? output << stream.readpartial(10240) end output end def dump_streams(command, streams) output = "$ #{command}\n" streams.each do |name, stream| unless stream.chomp.empty? output << "--- #{name} ---\n" output << "#{stream.chomp}\n" end end output << "\n" output end def debug(artifacts) artifacts = artifacts.dup artifacts.delete :status dump_streams(artifacts.delete(:command), artifacts) end def await_reload raise "no pid" if @application_pids.nil? || @application_pids.empty? Timeout.timeout(DEFAULT_TIMEOUT) do sleep 0.1 while @application_pids.any? { |p| process_alive?(p) } end end def run!(command, options = {}) attempts = (options.delete(:retry) || 0) + 1 artifacts = nil until attempts == 0 || artifacts && artifacts[:status].success? artifacts = run(command, options) attempts -= 1 end if artifacts[:status].success? artifacts else raise "command failed\n\n#{debug(artifacts)}" end end def bundle # Version restriction is a workaround for https://github.com/bundler/bundler/pull/4981 # The problem breaks our tests on Ruby 2.2 # We can remove once it's fixed run! "(gem list bundler | grep bundler) || gem install bundler --version '~> 1.12.0'", timeout: nil, retry: 2 run! "bundle check || bundle update --retry=2", timeout: nil end def insert_into_test(code) File.write(test, test.read.sub(/^\s*get .+$/, code)) end private def process_alive?(pid) Process.kill 0, pid true rescue Errno::ESRCH false end end end end spring-2.1.1/test/support/application_generator.rb000066400000000000000000000103711372105127500224130ustar00rootroot00000000000000module Spring module Test class ApplicationGenerator attr_reader :version_constraint, :version, :application def initialize(version_constraint) @version_constraint = version_constraint @version = RailsVersion.new(version_constraint.split(' ').last) @application = Application.new(root) @bundled = false @installed = false end def test_root Pathname.new Spring::Test.root end def root test_root.join("apps/rails-#{version.major}-#{version.minor}-spring-#{Spring::VERSION}") end def system(command) if ENV["SPRING_DEBUG"] puts "$ #{command}\n" else command = "(#{command}) > /dev/null" end Kernel.system(command) or raise "command failed: #{command}" puts if ENV["SPRING_DEBUG"] end def generate Bundler.with_clean_env { generate_files } install_spring generate_scaffold end # Sporadic SSL errors keep causing test failures so there are anti-SSL workarounds here def generate_files system("gem list '^rails$' --installed --version '#{version_constraint}' || " \ "gem install rails --clear-sources --source http://rubygems.org --version '#{version_constraint}'") @version = RailsVersion.new(`ruby -e 'puts Gem::Specification.find_by_name("rails", "#{version_constraint}").version'`.chomp) skips = %w(--skip-bundle --skip-javascript --skip-sprockets --skip-spring --skip-listen --skip-system-test) system("rails _#{version}_ new #{application.root} #{skips.join(' ')}") raise "application generation failed" unless application.exists? FileUtils.mkdir_p(application.gem_home) FileUtils.mkdir_p(application.user_home) FileUtils.rm_rf(application.path("test/performance")) append_to_file(application.gemfile, "gem 'spring', '#{Spring::VERSION}'") rewrite_file(application.gemfile) do |c| c.sub!("https://rubygems.org", "http://rubygems.org") c.gsub!(/(gem '(byebug|web-console|sdoc|jbuilder)')/, "# \\1") if @version.to_s < '5.2' c.gsub!(/(gem 'sqlite3')/, "# \\1") end c end if @version.to_s < '5.2' append_to_file(application.gemfile, "gem 'sqlite3', '< 1.4'") end rewrite_file(application.path("config/environments/test.rb")) do |c| c.sub!(/config\.cache_classes\s*=\s*true/, "config.cache_classes = false") c end if application.path("bin").exist? FileUtils.cp_r(application.path("bin"), application.path("bin_original")) end end def rewrite_file(file) File.write(file, yield(file.read)) end def append_to_file(file, add) rewrite_file(file) { |c| c << "#{add}\n" } end def generate_if_missing generate unless application.exists? end def install_spring return if @installed build_and_install_gems application.bundle FileUtils.rm_rf application.path("bin") if application.path("bin_original").exist? FileUtils.cp_r application.path("bin_original"), application.path("bin") end application.run! "#{application.spring} binstub --all" @installed = true end def manually_built_gems %w(spring) end def build_and_install_gems manually_built_gems.each do |name| spec = Gem::Specification.find_by_name(name) FileUtils.cd(spec.gem_dir) do FileUtils.rm(Dir.glob("#{name}-*.gem")) system("gem build #{name}.gemspec 2>&1") end application.run! "gem install #{spec.gem_dir}/#{name}-*.gem --no-doc", timeout: nil end end def copy_to(path) system("rm -rf #{path}") system("cp -r #{application.root} #{path}") end def generate_scaffold application.run! "bundle exec rails g scaffold post title:string" application.run! "bundle exec rake db:migrate db:test:prepare" end def gemspec(name) "#{Gem::Specification.find_by_name(name).gem_dir}/#{name}.gemspec" end end end end spring-2.1.1/test/support/rails_version.rb000066400000000000000000000005211372105127500207150ustar00rootroot00000000000000module Spring module Test class RailsVersion attr_reader :version def initialize(string) @version = Gem::Version.new(string) end def major version.segments[0] end def minor version.segments[1] end def to_s version.to_s end end end end spring-2.1.1/test/support/test.rb000066400000000000000000000005741372105127500170250ustar00rootroot00000000000000require "active_support" require "active_support/test_case" ActiveSupport.test_order = :random module Spring module Test class << self attr_accessor :root end require_relative "application" require_relative "application_generator" require_relative "rails_version" require_relative "watcher_test" require_relative "acceptance_test" end end spring-2.1.1/test/support/watcher_test.rb000066400000000000000000000105521372105127500205370ustar00rootroot00000000000000require "tmpdir" require "fileutils" require "timeout" require "active_support/core_ext/numeric/time" module Spring module Test class WatcherTest < ActiveSupport::TestCase runnables.delete self # prevent Minitest running this class LATENCY = 0.001 TIMEOUT = 1 attr_accessor :dir def watcher_class raise NotImplementedError end def watcher @watcher ||= watcher_class.new(dir, LATENCY) end def setup @dir = File.realpath(Dir.mktmpdir) end def teardown FileUtils.remove_entry_secure @dir watcher.stop end def touch(file, mtime = nil) options = {} options[:mtime] = mtime if mtime FileUtils.touch(file, options) end def assert_stale timeout = Time.now + TIMEOUT sleep LATENCY until watcher.stale? || Time.now > timeout assert watcher.stale? end def assert_not_stale sleep LATENCY * 10 assert !watcher.stale? end test "starting with no file" do file = "#{@dir}/omg" touch file, Time.now - 2.seconds watcher.start watcher.add file assert_not_stale touch file, Time.now assert_stale end test "is stale when a watched file is updated" do file = "#{@dir}/omg" touch file, Time.now - 2.seconds watcher.add file watcher.start assert_not_stale touch file, Time.now assert_stale end test "is stale when removing files" do file = "#{@dir}/omg" touch file, Time.now watcher.add file watcher.start assert_not_stale FileUtils.rm(file) assert_stale end test "is stale when files are added to a watched directory" do subdir = "#{@dir}/subdir" FileUtils.mkdir(subdir) watcher.add subdir watcher.start assert_not_stale touch "#{subdir}/foo", Time.now - 1.minute assert_stale end test "is stale when a file is changed in a watched directory" do subdir = "#{@dir}/subdir" FileUtils.mkdir(subdir) touch "#{subdir}/foo", Time.now - 1.minute watcher.add subdir watcher.start assert_not_stale touch "#{subdir}/foo", Time.now assert_stale end test "adding doesn't wipe stale state" do file = "#{@dir}/omg" file2 = "#{@dir}/foo" touch file, Time.now - 2.seconds touch file2, Time.now - 2.seconds watcher.add file watcher.start assert_not_stale touch file, Time.now watcher.add file2 assert_stale end test "on stale" do file = "#{@dir}/omg" touch file, Time.now - 2.seconds stale = false watcher.on_stale { stale = true } watcher.add file watcher.start touch file, Time.now Timeout.timeout(1) { sleep 0.01 until stale } assert stale # Check that we only get notified once stale = false sleep LATENCY * 3 assert !stale end test "add relative path" do File.write("#{dir}/foo", "foo") watcher.add "foo" assert_equal ["#{dir}/foo"], watcher.files.to_a end test "add dot relative path" do File.write("#{dir}/foo", "foo") watcher.add "./foo" assert_equal ["#{dir}/foo"], watcher.files.to_a end test "add non existent file" do watcher.add './foobar' assert watcher.files.empty? end test "add symlink" do File.write("#{dir}/bar", "bar") File.symlink("#{dir}/bar", "#{dir}/foo") watcher.add './foo' assert_equal ["#{dir}/bar"], watcher.files.to_a end test "add dangling symlink" do File.symlink("#{dir}/bar", "#{dir}/foo") watcher.add './foo' assert watcher.files.empty? end test "add directory with dangling symlink" do subdir = "#{@dir}/subdir" FileUtils.mkdir(subdir) File.symlink("dangling", "#{subdir}/foo") watcher.add subdir assert_not_stale # Adding a new file should mark as stale despite the dangling symlink. File.write("#{subdir}/new-file", "new") watcher.check_stale assert_stale end end end end spring-2.1.1/test/unit/000077500000000000000000000000001372105127500147565ustar00rootroot00000000000000spring-2.1.1/test/unit/client/000077500000000000000000000000001372105127500162345ustar00rootroot00000000000000spring-2.1.1/test/unit/client/help_test.rb000066400000000000000000000021621372105127500205510ustar00rootroot00000000000000require_relative "../../helper" require 'spring/client/command' require 'spring/client/help' require 'spring/client' class HelpTest < ActiveSupport::TestCase def spring_commands { 'command' => Class.new { def self.description 'Random Spring Command' end }, 'rails' => Class.new { def self.description "omg" end } } end def application_commands { 'random' => Class.new { def description 'Random Application Command' end }.new, 'hidden' => Class.new { def description nil end }.new } end def setup @help = Spring::Client::Help.new('help', spring_commands, application_commands) end test "formatted_help generates expected output" do expected_output = <<-EOF Version: #{Spring::VERSION} Usage: spring COMMAND [ARGS] Commands for Spring itself: command Random Spring Command Commands for your application: rails omg random Random Application Command EOF assert_equal expected_output.chomp, @help.formatted_help end end spring-2.1.1/test/unit/client/version_test.rb000066400000000000000000000005061372105127500213060ustar00rootroot00000000000000require_relative "../../helper" require 'spring/client' class VersionTest < ActiveSupport::TestCase test "outputs current version number" do version = Spring::Client::Version.new 'version' out, _ = capture_io do version.call end assert_equal "Spring version #{Spring::VERSION}", out.chomp end end spring-2.1.1/test/unit/commands_test.rb000066400000000000000000000052311372105127500201440ustar00rootroot00000000000000require_relative "../helper" require "spring/commands" class CommandsTest < ActiveSupport::TestCase test 'console command sets rails environment from command-line option' do command = Spring::Commands::RailsConsole.new assert_equal 'test', command.env(['test']) end test 'console command sets rails environment from -e option' do command = Spring::Commands::RailsConsole.new assert_equal 'test', command.env(['-e', 'test']) end test 'console command sets rails environment from --environment option' do command = Spring::Commands::RailsConsole.new assert_equal 'test', command.env(['--environment=test']) end test 'console command ignores first argument if it is a flag except -e and --environment' do command = Spring::Commands::RailsConsole.new assert_nil command.env(['--sandbox']) end test 'Runner#env sets rails environment from command-line option' do command = Spring::Commands::RailsRunner.new assert_equal 'test', command.env(['-e', 'test', 'puts 1+1']) end test 'RailsRunner#env sets rails environment from long form of command-line option' do command = Spring::Commands::RailsRunner.new assert_equal 'test', command.env(['--environment=test', 'puts 1+1']) end test 'RailsRunner#env ignores insignificant arguments' do command = Spring::Commands::RailsRunner.new assert_nil command.env(['puts 1+1']) end test 'RailsRunner#extract_environment removes -e ' do command = Spring::Commands::RailsRunner.new args = ['-b', '-a', '-e', 'test', '-r'] assert_equal [['-b', '-a', '-r'], 'test'], command.extract_environment(args) end test 'RailsRunner#extract_environment removes --environment=' do command = Spring::Commands::RailsRunner.new args = ['-b', '--environment=test', '-a', '-r'] assert_equal [['-b', '-a', '-r'], 'test'], command.extract_environment(args) end test "rake command has configurable environments" do command = Spring::Commands::Rake.new assert_nil command.env(["foo"]) assert_equal "test", command.env(["test"]) assert_equal "test", command.env(["test:models"]) assert_nil command.env(["test_foo"]) end test 'RailsTest#command defaults to test rails environment' do command = Spring::Commands::RailsTest.new assert_equal 'test', command.env([]) end test 'RailsTest#command sets rails environment from --environment option' do command = Spring::Commands::RailsTest.new assert_equal 'foo', command.env(['--environment=foo']) end test 'RailsTest#command sets rails environment from -e option' do command = Spring::Commands::RailsTest.new assert_equal 'foo', command.env(['-e', 'foo']) end end spring-2.1.1/test/unit/process_title_updater_test.rb000066400000000000000000000014021372105127500227420ustar00rootroot00000000000000require_relative "../helper" require "spring/process_title_updater" require "active_support/time" class ProcessTitleUpdaterTest < ActiveSupport::TestCase setup do @start = Time.local(2012, 2, 12, 4, 3, 12) @updater = Spring::ProcessTitleUpdater.new(@start) { } end test "seconds" do assert_equal "1 sec", @updater.distance_in_words(@start + 1.second) assert_equal "2 secs", @updater.distance_in_words(@start + 2.seconds) end test "minutes" do assert_equal "1 min", @updater.distance_in_words(@start + 1.minute + 10.seconds) assert_equal "5 mins", @updater.distance_in_words(@start + 5.minutes + 10.seconds) end test "hours" do assert_equal "6 hours", @updater.distance_in_words(@start + 6.hours + 50.minutes) end end spring-2.1.1/test/unit/watcher_test.rb000066400000000000000000000032211372105127500177750ustar00rootroot00000000000000require_relative "../helper" require_relative "../support/watcher_test" require "spring/watcher/polling" class PollingWatcherTest < Spring::Test::WatcherTest def watcher_class Spring::Watcher::Polling end test "skips staleness checks if already stale" do class << watcher attr_reader :checked_when_stale_count attr_reader :checked_when_not_stale_count def check_stale @checked_when_stale_count = 0 unless defined? @checked_when_stale_count @checked_when_not_stale_count = 0 unless defined? @checked_when_not_stale_count if stale? @checked_when_stale_count += 1 else @checked_when_not_stale_count += 1 end super end # Wait for the poller thread to finish. def join @poller.join if @poller end end # Track when we're marked as stale. on_stale_count = 0 watcher.on_stale { on_stale_count += 1 } # Add a file to watch and start polling. file = "#{@dir}/omg" touch file, Time.now - 2.seconds watcher.add file watcher.start assert watcher.running? # First touch bumps mtime and marks as stale. touch file, Time.now - 1.second Timeout.timeout(1) { watcher.join } assert !watcher.running? assert_equal 0, watcher.checked_when_stale_count assert_equal 1, watcher.checked_when_not_stale_count assert_equal 1, on_stale_count # Second touch skips mtime check because it's already stale. touch file, Time.now sleep 1 assert_equal 0, watcher.checked_when_stale_count assert_equal 1, watcher.checked_when_not_stale_count assert_equal 1, on_stale_count end end