commit 3325bce18b9cb5e926d999059fd4be5fe24ca653 Author: John Bintz Date: Wed Feb 1 10:06:30 2012 -0500 initial fun commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d87d4be --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..e57100d --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in vegetable_glue.gemspec +gemspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..87b491e --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 John Bintz + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..df4a337 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# VegetableGlue + +Easy way to start/stop/restart a dependent Rails API app in a consumer's acceptance tests. +Uses `database_cleaner`'s `:truncation` mode to clean out your databases after each run. + +## Installation & Usage + +In both apps' Gemfiles: + +``` ruby +gem 'vegetable_glue' +``` + +In the provider (API): + +* Create or use a Rails environment that has a database that you don't mind nuking (say `test`, `acceptance`, or `cucumber`) +* Modify `config/routes.rb`: + +``` ruby +require 'vegetable_glue/routes' + +MyApp::Application.routes.draw do + # ... your other routes ... + + acceptance_helper_routes #=> for the :acceptance env, or + acceptance_helper_routes_for :cucumber #=> for the :cucumber env +end + +The two additional routes are only added in that environment. + +In the consumer (Frontend): + +* For Cucumber, add the following to `features/support/env.rb`: + +``` ruby +require 'vegetable_glue/cucumber' + +VegetableGlue.url = 'http://localhost:6161/' #=> include the port in here, too, that's where the app will run +VegetableGlue.path = '../path/to/the/app' +``` + +The app will clean its database on each scenario. To restart the app, pass in the environment variable `GLUE_RESTART`: + + GLUE_RESTART=true bundle exec cucumber + +If you're using ActiveResource, a good source of the URL is `ActiveResource::Base.site`. + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f57ae68 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" diff --git a/lib/vegetable_glue.rb b/lib/vegetable_glue.rb new file mode 100644 index 0000000..188e28a --- /dev/null +++ b/lib/vegetable_glue.rb @@ -0,0 +1,33 @@ +require "vegetable_glue/version" +require 'net/http' +require 'fileutils' + +module VegetableGlue + autoload :Runner, 'vegetable_glue/runner' + + ACCEPTANCE = '__acceptance__' + CLEAN = '__clean__' + + class << self + attr_accessor :url, :path, :env + + def shutdown + Runner.new(options).shutdown + end + + def clean + Runner.new(options).clean + end + + def env + @env ||= :cucumber + end + + private + def options + { :url => url, :path => path, :env => env } + end + end +end + + diff --git a/lib/vegetable_glue/cucumber.rb b/lib/vegetable_glue/cucumber.rb new file mode 100644 index 0000000..915e93c --- /dev/null +++ b/lib/vegetable_glue/cucumber.rb @@ -0,0 +1,11 @@ +Before do + if ENV['GLUE_RESTART'] + VegetableGlue.shutdown + + ENV.delete('GLUE_RESTART') + end + + VegetableGlue.clean +end + + diff --git a/lib/vegetable_glue/routes.rb b/lib/vegetable_glue/routes.rb new file mode 100644 index 0000000..0313b2d --- /dev/null +++ b/lib/vegetable_glue/routes.rb @@ -0,0 +1,20 @@ +class ActionDispatch::Routing::Mapper + def acceptance_helper_routes + acceptance_helper_routes_for + end + + def acceptance_helper_routes_for(env = :acceptance) + if Rails.env.to_sym == env + get VegetableGlue::ACCEPTANCE => lambda { |env| [ 200, {}, [ VegetableGlue::ACCEPTANCE ] ] } + + get VegetableGlue::CLEAN => lambda { |env| + require 'database_cleaner' + + DatabaseCleaner.clean_with :truncation + + [ 200, {}, [ VegetableGlue::CLEAN ] ] + } + end + end +end + diff --git a/lib/vegetable_glue/runner.rb b/lib/vegetable_glue/runner.rb new file mode 100644 index 0000000..6ea8adc --- /dev/null +++ b/lib/vegetable_glue/runner.rb @@ -0,0 +1,105 @@ +module VegetableGlue + class Runner + attr_reader :options + + def initialize(options = {}) + @options = options + end + + def ensure_running + port = options[:url].port + + result = nil + + begin + result = get_acceptance + rescue Errno::ECONNREFUSED => e + $stdout.puts "Starting #{app_name}..." + + Bundler.with_clean_env do + system %{cd #{options[:path]} && bundle exec rails server -p #{port} -d -e #{options[:env]} -P #{pid_path}} + end + + raise StandardError.new("#{app_name} did not start up in 30 seconds!") if !(result = wait_for_up) + end + + if result == VegetableGlue::ACCEPTANCE + $stdout.puts "#{app_name} running on port #{port}" + else + raise StandardError.new("Is #{app_name} running? You should have included the routes with `acceptance_helper_routes`") + end + end + + def shutdown + if File.file?(pid_path) + system %{kill -INT #{File.read(pid_path)}} + + wait_for_down + + $stdout.puts "#{app_name} shut down" + end + + FileUtils.rm_f pid_path + end + + def clean + ensure_running + + result = Net::HTTP.get(options[:url].merge(URI("/#{VegetableGlue::CLEAN}"))) + + raise StandardError.new("#{app_name} database not cleaned") if result != VegetableGlue::CLEAN + end + + def pid_path + File.join(File.expand_path(options[:path]), "tmp/pids/#{options[:env]}.pid") + end + + private + def get_acceptance + Net::HTTP.get(options[:url].merge(URI("/#{VegetableGlue::ACCEPTANCE}"))) + end + + def app_name + File.basename(options[:path]) + end + + def wait_for_down + times = 30 + + while times > 0 + begin + Net::HTTP.get(options[:url]) + rescue Errno::ECONNREFUSED, Errno::ECONNRESET + return true + end + + times -= 1 + + sleep 1 + end + + raise StandardError.new("#{app_name} did not shut down") + end + + def wait_for_up + times = 30 + result = nil + + while times > 0 + begin + result = get_acceptance + rescue Errno::ECONNREFUSED, Errno::ECONNRESET + end + + break if result + + sleep 1 + + times -= 1 + end + + result + end + end +end + diff --git a/lib/vegetable_glue/version.rb b/lib/vegetable_glue/version.rb new file mode 100644 index 0000000..2c8daae --- /dev/null +++ b/lib/vegetable_glue/version.rb @@ -0,0 +1,3 @@ +module VegetableGlue + VERSION = "0.0.1" +end diff --git a/vegetable_glue.gemspec b/vegetable_glue.gemspec new file mode 100644 index 0000000..ed50ac6 --- /dev/null +++ b/vegetable_glue.gemspec @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/vegetable_glue/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["John Bintz"] + gem.email = ["john@coswellproductions.com"] + gem.description = %q{TODO: Write a gem description} + gem.summary = %q{TODO: Write a gem summary} + gem.homepage = "" + + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.name = "vegetable_glue" + gem.require_paths = ["lib"] + gem.version = VegetableGlue::VERSION + + gem.add_dependency 'database_cleaner' +end