From b30d56819e1dd13ad83e8f1c3152e8d3ee778e12 Mon Sep 17 00:00:00 2001 From: John Bintz Date: Mon, 9 Jan 2012 10:48:47 -0500 Subject: [PATCH] implement switching back to prior env on post-commit, should fix #2 --- Gemfile | 4 + Guardfile | 17 ++- Rakefile | 21 ++- bin/penchant | 12 +- features/cli.feature | 18 +++ features/gemfile.feature | 33 ++++ .../given/i_have_the_file_with_content.rb | 5 + .../then/the_file_should_have_content.rb | 3 + .../then/the_output_should_include.rb | 3 + .../i_rebuild_the_gemfile_switching_back.rb | 3 + .../i_rebuild_the_gemfile_with_deployment.rb | 3 + features/step_definitions/when/i_run_in.rb | 3 + features/support/env.rb | 13 ++ lib/penchant/gemfile.rb | 70 ++++++--- spec/lib/penchant/gemfile_spec.rb | 144 ++++++++++++++++++ template/script/hooks/post-commit | 2 +- 16 files changed, 327 insertions(+), 27 deletions(-) create mode 100644 features/cli.feature create mode 100644 features/gemfile.feature create mode 100644 features/step_definitions/given/i_have_the_file_with_content.rb create mode 100644 features/step_definitions/then/the_file_should_have_content.rb create mode 100644 features/step_definitions/then/the_output_should_include.rb create mode 100644 features/step_definitions/when/i_rebuild_the_gemfile_switching_back.rb create mode 100644 features/step_definitions/when/i_rebuild_the_gemfile_with_deployment.rb create mode 100644 features/step_definitions/when/i_run_in.rb create mode 100644 features/support/env.rb diff --git a/Gemfile b/Gemfile index 3ccc20a..a71c8ab 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,11 @@ gemspec gem 'guard' gem 'guard-rspec' +gem 'guard-cucumber' + gem 'mocha' gem 'fakefs' gem 'rspec', '~> 2.6.0' gem 'rake' + +gem 'cucumber' diff --git a/Guardfile b/Guardfile index 649ad30..fbb8889 100644 --- a/Guardfile +++ b/Guardfile @@ -1,9 +1,18 @@ # A sample Guardfile # More info at https://github.com/guard/guard#readme -guard 'rspec', :cli => '-c', :version => 2 do - watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { "spec" } +group :rspec do + guard 'rspec', :cli => '-c', :version => 2 do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } + end end +group :cucumber do + guard 'cucumber' do + watch(%r{^features/.+\.feature$}) + watch(%r{^features/support/.+$}) { 'features' } + watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' } + end +end diff --git a/Rakefile b/Rakefile index 8ce01f9..1040ca8 100644 --- a/Rakefile +++ b/Rakefile @@ -9,5 +9,24 @@ rescue LoadError "#$! - no rspec" end -task :default => :spec +begin + require 'rspec/core/rake_task' + + RSpec::Core::RakeTask.new(:spec) +rescue LoadError + "#$! - no rspec" +end + +begin + require 'cucumber' + require 'cucumber/rake/task' + + Cucumber::Rake::Task.new(:cucumber) do |t| + t.cucumber_opts = "features --format pretty" + end +rescue LoadError + "#$! - no cucumber" +end + +task :default => [ :spec, :cucumber ] diff --git a/bin/penchant b/bin/penchant index de3e4c7..0da8a8a 100755 --- a/bin/penchant +++ b/bin/penchant @@ -29,11 +29,19 @@ class PenchantCLI < Thor end method_options :deployment => false + method_options :switch_back => false desc "gemfile ENV", "Switch the gemfile environment, or rebuild the current environment if not given" def gemfile(env = get_current_env) if env - puts "[penchant] Rebunding for #{env} environment#{options[:deployment] ? ", deployment mode" : ''}..." - Penchant::Gemfile.do_full_env_switch!(env, options[:deployment]) + if options[:switch_back] + puts "[penchant] Switching back, fallback: #{env}..." + + Penchant::Gemfile.switch_back!(env) + else + puts "[penchant] Rebunding for #{env} environment#{options[:deployment] ? ", deployment mode" : ''}..." + + Penchant::Gemfile.do_full_env_switch!(env, options[:deployment]) + end end gemfile = Penchant::Gemfile.new diff --git a/features/cli.feature b/features/cli.feature new file mode 100644 index 0000000..16fbbb6 --- /dev/null +++ b/features/cli.feature @@ -0,0 +1,18 @@ +Feature: CLI + Scenario: Switch back to the original pre-deployment environment + Given I have the file "tmp/Gemfile.erb" with the content: + """ + gem 'rake' + """ + And I have the file "tmp/Gemfile" with the content: + """ + # generated by penchant, environment: production, deployment mode (was local) + """ + When I run "bin/penchant gemfile other --switch-back" in the "tmp" directory + Then the file "tmp/Gemfile" should have the following content: + """ + # generated by penchant, environment: local + gem 'rake' + """ + And the output should include "fallback: other" + diff --git a/features/gemfile.feature b/features/gemfile.feature new file mode 100644 index 0000000..0886c07 --- /dev/null +++ b/features/gemfile.feature @@ -0,0 +1,33 @@ +@fakefs +Feature: Gemfiles + Scenario: When rebuilding for deployment, save the original state + Given I have the file "Gemfile.erb" with the content: + """ + this is content + """ + And I have the file "Gemfile" with the content: + """ + # generated by penchant, environment: local + """ + When I rebuild the Gemfile for "production" mode with deployment + Then the file "Gemfile" should have the following content: + """ + # generated by penchant, environment: production, deployment mode (was local) + this is content + """ + + Scenario: When unbundling from deployment with an original state, switch to that state + Given I have the file "Gemfile.erb" with the content: + """ + this is content + """ + And I have the file "Gemfile" with the content: + """ + # generated by penchant, environment: production, deployment mode (was local) + """ + When I rebuild the Gemfile asking to switch back to the previous state + Then the file "Gemfile" should have the following content: + """ + # generated by penchant, environment: local + this is content + """ diff --git a/features/step_definitions/given/i_have_the_file_with_content.rb b/features/step_definitions/given/i_have_the_file_with_content.rb new file mode 100644 index 0000000..67d69a4 --- /dev/null +++ b/features/step_definitions/given/i_have_the_file_with_content.rb @@ -0,0 +1,5 @@ +Given /^I have the file "([^"]*)" with the content:$/ do |file, string| + FileUtils.mkdir_p File.dirname(file) + + File.open(file, 'wb') { |fh| fh.print string } +end diff --git a/features/step_definitions/then/the_file_should_have_content.rb b/features/step_definitions/then/the_file_should_have_content.rb new file mode 100644 index 0000000..ee98df3 --- /dev/null +++ b/features/step_definitions/then/the_file_should_have_content.rb @@ -0,0 +1,3 @@ +Then /^the file "([^"]*)" should have the following content:$/ do |file, string| + File.read(file).should == string +end diff --git a/features/step_definitions/then/the_output_should_include.rb b/features/step_definitions/then/the_output_should_include.rb new file mode 100644 index 0000000..5dcf8a8 --- /dev/null +++ b/features/step_definitions/then/the_output_should_include.rb @@ -0,0 +1,3 @@ +Then /^the output should include "([^"]*)"$/ do |text| + @output.should include(text) +end diff --git a/features/step_definitions/when/i_rebuild_the_gemfile_switching_back.rb b/features/step_definitions/when/i_rebuild_the_gemfile_switching_back.rb new file mode 100644 index 0000000..ceab173 --- /dev/null +++ b/features/step_definitions/when/i_rebuild_the_gemfile_switching_back.rb @@ -0,0 +1,3 @@ +When /^I rebuild the Gemfile asking to switch back to the previous state$/ do + Penchant::Gemfile.switch_back!("remote") +end diff --git a/features/step_definitions/when/i_rebuild_the_gemfile_with_deployment.rb b/features/step_definitions/when/i_rebuild_the_gemfile_with_deployment.rb new file mode 100644 index 0000000..92cc2ac --- /dev/null +++ b/features/step_definitions/when/i_rebuild_the_gemfile_with_deployment.rb @@ -0,0 +1,3 @@ +When /^I rebuild the Gemfile for "([^"]*)" mode with deployment$/ do |env| + Penchant::Gemfile.do_full_env_switch!(env, true) +end diff --git a/features/step_definitions/when/i_run_in.rb b/features/step_definitions/when/i_run_in.rb new file mode 100644 index 0000000..1fd72ec --- /dev/null +++ b/features/step_definitions/when/i_run_in.rb @@ -0,0 +1,3 @@ +When /^I run "([^"]*)" in the "([^"]*)" directory$/ do |command, dir| + @output = %x{bash -c 'opwd=$PWD; cd #{dir} && $opwd/#{command}'} +end diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..3342870 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,13 @@ +require 'fakefs/safe' +require 'penchant' + +Before('@fakefs') do + FakeFS.activate! +end + +After do + FakeFS.deactivate! + + FileUtils.rm_rf 'tmp' +end + diff --git a/lib/penchant/gemfile.rb b/lib/penchant/gemfile.rb index ce4e574..c11aeee 100644 --- a/lib/penchant/gemfile.rb +++ b/lib/penchant/gemfile.rb @@ -2,21 +2,30 @@ require 'erb' module Penchant class Gemfile - attr_reader :path + attr_reader :path, :is_deployment - class << self - def do_full_env_switch!(env, deployment = false) - gemfile = Penchant::Gemfile.new - gemfile.run_dot_penchant!(env, deployment) + def self.do_full_env_switch!(env, deployment = false) + return false if !(gemfile = pre_switch(env, deployment)) - if !gemfile.has_gemfile_erb? - return false - end - - gemfile.switch_to!(env, deployment) - end + gemfile.switch_to!(env, deployment) end + def self.switch_back!(fallback_env) + return false if !(gemfile = pre_switch(fallback_env)) + + gemfile.switch_back!(fallback_env) + end + + def self.pre_switch(env, deployment = false) + gemfile = Penchant::Gemfile.new + return false if !gemfile.has_gemfile_erb? + gemfile.run_dot_penchant!(env, deployment) + + gemfile + end + + def current_env ; @env ; end + def initialize(path = Dir.pwd) @path = path end @@ -26,7 +35,7 @@ module Penchant end def has_gemfile? - File.file?('Gemfile') + File.file?(gemfile_path) end def has_dot_penchant? @@ -42,33 +51,52 @@ module Penchant end def environment - File.readlines(gemfile_path).first.strip[%r{environment: ([^, ]*)}, 1] + gemfile_header.strip[%r{environment: ([^, ]*)}, 1] end def deployment? - File.readlines(gemfile_path).first['deployment mode'] != nil + gemfile_header['deployment mode'] != nil end def switch_to!(gemfile_env = nil, deployment = false) @env, @is_deployment = gemfile_env, deployment - template = File.read(gemfile_erb_path) - File.open(gemfile_path, 'wb') do |fh| - fh.puts "# generated by penchant, environment: #{@env || "none"}#{@is_deployment ? " , deployment mode" : ""}" + output = [ header, ERB.new(template).result(binding) ] - fh.print ERB.new(template).result(binding) - end + File.open(gemfile_path, 'wb') { |fh| fh.print output.join("\n") } end def run_dot_penchant!(env, deployment) DotPenchant.run(env || environment, deployment) if has_dot_penchant? end + def header + header = [ "# generated by penchant, environment: #{current_env}" ] + + if is_deployment + header << ", deployment mode (was #{environment})" + end + + header.join + end + + def prior_environment + gemfile_header[%r{\(was (.+)\)}, 1] + end + + def switch_back!(fallback_env) + switch_to!(prior_environment || fallback_env) + end + private def file_in_path(file) File.join(@path, file) end + def template + File.read(gemfile_erb_path) + end + def env(check, &block) instance_eval(&block) if check.to_s == @env.to_s end @@ -76,6 +104,10 @@ module Penchant def no_deployment(&block) instance_eval(&block) if !@is_deployment end + + def gemfile_header + (has_gemfile? and File.readlines(gemfile_path).first) or "" + end end end diff --git a/spec/lib/penchant/gemfile_spec.rb b/spec/lib/penchant/gemfile_spec.rb index e743c99..d851d7b 100644 --- a/spec/lib/penchant/gemfile_spec.rb +++ b/spec/lib/penchant/gemfile_spec.rb @@ -146,5 +146,149 @@ ERB end end end + + describe '#switch_to!' do + let(:template) { 'template' } + let(:gemfile_path) { 'gemfile path' } + let(:header) { 'header' } + + let(:gemfile_out) { File.read(gemfile_path) } + + before do + gemfile.stubs(:template).returns(template) + gemfile.stubs(:gemfile_path).returns(gemfile_path) + + gemfile.expects(:header).returns(header) + end + + it 'should write out the new gemfile' do + gemfile.switch_to! + + gemfile_out.should include(template) + gemfile_out.should include(header) + end + end + + describe '#header' do + subject { gemfile.header } + + let(:env) { 'env' } + let(:prior_environment) { 'prior' } + + before do + gemfile.stubs(:current_env).returns(env) + gemfile.stubs(:environment).returns(prior_environment) + end + + context 'not deployment' do + before do + gemfile.stubs(:is_deployment).returns(false) + end + + it { should == "# generated by penchant, environment: #{env}" } + end + + context 'deployment' do + before do + gemfile.stubs(:is_deployment).returns(true) + end + + it { should == "# generated by penchant, environment: #{env}, deployment mode (was #{prior_environment})" } + end + end + + describe '#prior_environment' do + subject { gemfile.prior_environment } + + let(:prior) { 'prior' } + + before do + gemfile.stubs(:gemfile_header).returns("# header (was #{prior})") + end + + it { should == prior } + end + + describe '.switch_back!' do + let(:gemfile) { stub } + let(:fallback_env) { 'env' } + + context 'pre_switch fails' do + before do + described_class.stubs(:pre_switch).returns(false) + + gemfile.expects(:switch_back!).never + end + + it 'should not switch back' do + described_class.switch_back!(fallback_env).should be_false + end + end + + context 'pre_switch succeeds' do + before do + described_class.stubs(:pre_switch).returns(gemfile) + + gemfile.expects(:switch_back!).with(fallback_env) + end + + it 'should switch back' do + described_class.switch_back!(fallback_env) + end + end + end + + describe '.pre_switch' do + subject { described_class.pre_switch(env, deployment) } + + let(:env) { 'env' } + let(:deployment) { 'deployment' } + + context 'no Gemfile.erb' do + before do + described_class.any_instance.expects(:has_gemfile_erb?).returns(false) + end + + it { should be_false } + end + + context 'Gemfile.erb' do + before do + described_class.any_instance.expects(:has_gemfile_erb?).returns(true) + described_class.any_instance.expects(:run_dot_penchant!).with(env, deployment) + end + + it { should be_a_kind_of(described_class) } + end + end + + describe '#switch_back!' do + let(:fallback_env) { 'fallback' } + let(:prior) { 'prior' } + + context 'no prior' do + before do + gemfile.stubs(:prior_environment).returns(nil) + + gemfile.expects(:switch_to!).with(fallback_env) + end + + it 'should proxy through to switch_to!' do + gemfile.switch_back!(fallback_env) + end + end + + context 'prior' do + before do + gemfile.stubs(:prior_environment).returns(prior) + + gemfile.expects(:switch_to!).with(prior) + end + + it 'should proxy through to switch_to!' do + gemfile.switch_back!(fallback_env) + end + end + end end diff --git a/template/script/hooks/post-commit b/template/script/hooks/post-commit index 2231ee1..490f85d 100755 --- a/template/script/hooks/post-commit +++ b/template/script/hooks/post-commit @@ -1,4 +1,4 @@ #!/bin/bash -penchant gemfile remote +penchant gemfile remote --switch-back