bumps 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+
3
+ module Bumps
4
+ class Configuration
5
+
6
+ def self.method_missing method, *args, &block
7
+ singleton.send method, *args, &block
8
+ end
9
+
10
+ def self.singleton
11
+ @singleton = Configuration.new unless defined? @singleton
12
+ @singleton
13
+ end
14
+
15
+ def initialize
16
+ @config = {
17
+ :output_stream => STDOUT,
18
+ :results_formatter => Cucumber::Formatter::Html
19
+ }
20
+ end
21
+
22
+ def method_missing method, *args
23
+ return @config[method] if @config.has_key?(method)
24
+ method.to_s.end_with?('=') ? @config[method.to_s.chop.to_sym] = args[0] : super(method, args)
25
+ end
26
+
27
+ def configure &block
28
+ instance_eval(&block)
29
+ end
30
+
31
+ def use_server server
32
+ @config[:pull_url] = URI.join(server, 'features/content').to_s
33
+ @config[:push_url] = URI.join(server, 'features/results').to_s
34
+ end
35
+
36
+ def push_to url
37
+ @config[:push_url] = url
38
+ end
39
+
40
+ def pull_from url
41
+ @config[:pull_url] = url
42
+ end
43
+
44
+ def format_results_with formatter_class
45
+ @config[:results_formatter] = formatter_class
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ module Bumps
2
+ class Feature
3
+
4
+ attr_accessor :name, :content
5
+
6
+ def initialize
7
+ @name = ''
8
+ @content = ''
9
+ end
10
+
11
+ def self.pull
12
+ Configuration.output_stream << "\nRetrieving features from #{Configuration.pull_url} ...\n"
13
+ begin
14
+ features = RemoteFeature.fetch(Configuration.pull_url)
15
+ rescue Exception => e
16
+ Configuration.output_stream << "\nCould not pull features: #{e}\n"
17
+ features = []
18
+ end
19
+
20
+ features.each do |feature|
21
+ feature.write_to Configuration.feature_directory
22
+ end
23
+ Configuration.output_stream << "Wrote #{features.size} features to #{Configuration.feature_directory}\n\n"
24
+ end
25
+
26
+ def write_to directory
27
+ write_content_to absolute_path_under(directory)
28
+ end
29
+
30
+ def write_content_to file_path
31
+ FileUtils.makedirs File.dirname(file_path)
32
+ File.open(file_path, 'w') {|f| f.write(content) }
33
+ end
34
+
35
+ def absolute_path_under directory
36
+ expanded_directory = File.expand_path directory
37
+ file_path = File.expand_path(File.join(directory, name))
38
+ unless file_path.start_with? expanded_directory
39
+ raise "Could not write feature to path #{file_path}, path is not below #{expanded_directory}"
40
+ end
41
+ file_path
42
+ end
43
+
44
+ def eql? match
45
+ self.instance_variables.each do |attr|
46
+ self_attr = self.instance_variable_get(attr)
47
+ match_attr = match.instance_variable_get(attr)
48
+ return false unless self_attr == match_attr
49
+ end
50
+
51
+ true
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ module Bumps::HookTasks
2
+
3
+ SetFeatureDirectoryTask = lambda do
4
+ # nasty? hell yeah. got any better ideas? need access to that protected method...
5
+ feature_directories = configuration.send :feature_dirs
6
+
7
+ error_message = 'More than one feature directory/file was specified. ' +
8
+ 'Please only specify a single feature directory when using bumps'
9
+ raise error_message if feature_directories.size > 1
10
+ Bumps::Configuration.feature_directory = feature_directories.first
11
+ end
12
+
13
+ PullFeaturesTask = lambda do
14
+ Bumps::Feature.pull
15
+ end
16
+
17
+ RegisterPushFormatterTask = lambda do
18
+ configuration.options[:formats]['Bumps::ResultsPushFormatter'] = Bumps::Configuration.output_stream
19
+ end
20
+
21
+ SetOutputStreamTask = lambda do
22
+ Bumps::Configuration.output_stream = @out_stream
23
+ end
24
+
25
+ end
@@ -0,0 +1,27 @@
1
+ module Bumps
2
+ class PreFeatureLoadHook
3
+ def self.register_on clazz
4
+ clazz.class_eval do
5
+
6
+ alias_method :original_load_plain_text_features, :load_plain_text_features
7
+
8
+ def bumps_load_plain_text_features
9
+ Bumps::PreFeatureLoadHook.tasks.each { |task| instance_eval(&task) }
10
+ original_load_plain_text_features
11
+ end
12
+
13
+ alias_method :load_plain_text_features, :bumps_load_plain_text_features
14
+
15
+ end
16
+ end
17
+
18
+ def self.tasks
19
+ [
20
+ HookTasks::SetFeatureDirectoryTask,
21
+ Bumps::HookTasks::SetOutputStreamTask,
22
+ HookTasks::PullFeaturesTask,
23
+ HookTasks::RegisterPushFormatterTask
24
+ ]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'nokogiri'
2
+
3
+ require 'open-uri'
4
+
5
+ module Bumps
6
+
7
+ class RemoteFeature
8
+
9
+ def self.fetch location
10
+ parse(open(location){|f| f.read})
11
+ end
12
+
13
+ def self.parse xml
14
+ document = Nokogiri::XML xml
15
+ document.search('feature').collect do |feature_element|
16
+ feature = Feature.new
17
+ feature.content = feature_element.text.strip
18
+ feature.name = feature_element.attribute('name').to_s
19
+ feature
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ require 'cucumber/formatter/html'
2
+ require 'net/http'
3
+
4
+ module Bumps
5
+ class ResultsPushFormatter < Cucumber::Ast::Visitor
6
+
7
+ def initialize(step_mother, io, options)
8
+ super step_mother
9
+ @step_mother = step_mother
10
+ @io = io
11
+ @options = options
12
+ end
13
+
14
+ def visit_features features
15
+ push results_of_running(features)
16
+ end
17
+
18
+ def push results
19
+ uri = URI.parse(Bumps::Configuration.push_url)
20
+ response, body = Net::HTTP.post_form uri, {:results => results}
21
+ if response.code_type == Net::HTTPOK
22
+ @io << "Successfully pushed results to #{Bumps::Configuration.push_url}\n\n"
23
+ else
24
+ @io << "Failed to push results to #{Bumps::Configuration.push_url} - HTTP #{response.code}: \n#{response.body}\n\n"
25
+ end
26
+ end
27
+
28
+ def results_of_running features
29
+ StringIO.open do |io|
30
+ formatter = Bumps::Configuration.results_formatter.new step_mother, io, options
31
+ formatter.visit_features features
32
+
33
+ io.string
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/bumps.rb'}"
9
+ puts "Loading bumps gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,78 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe Bumps::Configuration do
4
+
5
+ before {@output_stream = mock('output stream').as_null_object}
6
+ subject {Bumps::Configuration.new}
7
+
8
+ it 'should provide access to the output stream' do
9
+ out_stream = mock 'out stream'
10
+ subject.output_stream = out_stream
11
+
12
+ subject.output_stream.should == out_stream
13
+ end
14
+
15
+ it 'should allow configuration using a block' do
16
+ subject.should_receive(:configuration_call)
17
+
18
+ subject.configure { configuration_call }
19
+ end
20
+
21
+ it 'should derive pull URL from server' do
22
+ subject.use_server 'http://server'
23
+
24
+ subject.pull_url.should == 'http://server/features/content'
25
+ end
26
+
27
+ it 'should derive push URL from server' do
28
+ subject.use_server 'http://server'
29
+
30
+ subject.push_url.should == 'http://server/features/results'
31
+ end
32
+
33
+ it 'should be able to handle a server URL with a trailing slash' do
34
+ subject.use_server 'http://server/'
35
+
36
+ subject.pull_url.should match(/^http:\/\/server\/[a-z]/)
37
+ end
38
+
39
+ it 'should allow a non-default push URL to be specified' do
40
+ subject.push_to 'http://url.com'
41
+
42
+ subject.push_url.should == 'http://url.com'
43
+ end
44
+
45
+ it 'should allow a non-default pull URL to be specified' do
46
+ subject.pull_from 'http://url.com'
47
+
48
+ subject.pull_url.should == 'http://url.com'
49
+ end
50
+
51
+ it 'should allow the feature directory to be set' do
52
+ subject.feature_directory = 'feature_directory'
53
+
54
+ subject.feature_directory.should == 'feature_directory'
55
+ end
56
+
57
+ it 'should allow results formatter to be specified' do
58
+ formatter_class = mock 'formatter class'
59
+
60
+ subject.format_results_with formatter_class
61
+
62
+ subject.results_formatter.should == formatter_class
63
+ end
64
+
65
+ it 'should default the results formatter to Cucumber HTML formatter' do
66
+ subject.results_formatter.should == Cucumber::Formatter::Html
67
+ end
68
+
69
+ it 'should allow access to configuration via class' do
70
+ singleton = mock 'singleton'
71
+ Bumps::Configuration.stub!(:singleton).and_return singleton
72
+
73
+ singleton.should_receive(:configuration_property=).with 'arg'
74
+
75
+ Bumps::Configuration.configuration_property = 'arg'
76
+ end
77
+
78
+ end
@@ -0,0 +1,119 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe Bumps::Feature do
4
+ describe 'when pulling' do
5
+
6
+ before do
7
+ @output_stream = mock('output stream').as_null_object
8
+ Bumps::Configuration.stub!(:output_stream).and_return @output_stream
9
+ end
10
+
11
+ subject {Bumps::Feature}
12
+
13
+ it 'should write fetched features to the feature directory' do
14
+ Bumps::Configuration.stub!(:feature_directory).and_return 'feature_directory'
15
+ Bumps::Configuration.stub!(:pull_url).and_return 'location'
16
+
17
+ features = 3.times.collect do |index|
18
+ feature = mock "feature #{index}"
19
+ feature.should_receive(:write_to).with 'feature_directory'
20
+ feature
21
+ end
22
+
23
+ Bumps::RemoteFeature.stub!(:fetch).with('location').and_return features
24
+
25
+ Bumps::Configuration.stub!(:feature_directory).and_return 'feature_directory'
26
+
27
+ subject.pull
28
+ end
29
+
30
+ it 'should output an error message if the features could not be fetched' do
31
+ Bumps::Configuration.stub! :pull_url
32
+ Bumps::Configuration.stub! :feature_directory
33
+ Bumps::RemoteFeature.stub!(:fetch).and_raise "exception message"
34
+
35
+ @output_stream.should_receive(:<<).with "\nCould not pull features: exception message\n"
36
+
37
+ subject.pull
38
+ end
39
+
40
+ it 'should display which location the features are being retrieved from' do
41
+ Bumps::RemoteFeature.stub!(:fetch).and_return []
42
+ Bumps::Configuration.stub!(:pull_url).and_return 'pull_url'
43
+ Bumps::Configuration.stub! :feature_directory
44
+
45
+ @output_stream.should_receive(:<<).with "\nRetrieving features from pull_url ...\n"
46
+
47
+ subject.pull
48
+ end
49
+
50
+ it 'should display the total number of features retrieved and location they were written to' do
51
+ features = 3.times.collect{|index| mock("feature #{index}").as_null_object}
52
+ Bumps::RemoteFeature.stub!(:fetch).and_return features
53
+ Bumps::Configuration.stub!(:feature_directory).and_return 'feature_directory'
54
+ Bumps::Configuration.stub! :pull_url
55
+
56
+ @output_stream.should_receive(:<<).with "Wrote 3 features to feature_directory\n\n"
57
+
58
+ subject.pull
59
+ end
60
+ end
61
+
62
+ describe 'when writing self to file' do
63
+
64
+ it 'should determine absolute path before writing contents' do
65
+ subject.stub(:absolute_path_under).with('directory').and_return 'path'
66
+
67
+ subject.should_receive(:write_content_to).with 'path'
68
+
69
+ subject.write_to 'directory'
70
+ end
71
+ end
72
+
73
+ describe 'when determining absolute feature file path' do
74
+
75
+ it 'should construct file name from expanded directory and feature name' do
76
+ subject.stub!(:name).and_return 'name'
77
+
78
+ subject.absolute_path_under('/a/b/c/..').should == '/a/b/name'
79
+ end
80
+
81
+ it 'should fail if given path does not resolve to one below the feature directory' do
82
+ subject.stub!(:name).and_return '../../etc/bashrc'
83
+ File.stub! :open # just in case
84
+
85
+ lambda {subject.absolute_path_under '/stuff/features'}.should raise_error 'Could not write feature to path /etc/bashrc, path is not below /stuff/features'
86
+ end
87
+ end
88
+
89
+ describe 'when writing content' do
90
+
91
+ it 'should overwrite existing files' do
92
+ FileUtils.stub! :makedirs
93
+
94
+ File.should_receive(:open).with anything, 'w'
95
+
96
+ subject.write_content_to ''
97
+ end
98
+
99
+ it 'should force the creation of directories in the feature name' do
100
+ File.stub! :open
101
+
102
+ FileUtils.should_receive(:makedirs).with 'features_dir/subdir'
103
+
104
+ subject.write_content_to 'features_dir/subdir/featurename.feature'
105
+ end
106
+
107
+ it 'should write content to file' do
108
+ FileUtils.stub! :makedirs
109
+ @file = mock 'file'
110
+ File.stub!(:open).and_yield @file
111
+ subject.stub!(:content).and_return 'content'
112
+
113
+ @file.should_receive(:write).with 'content'
114
+
115
+ subject.write_content_to ''
116
+ end
117
+ end
118
+
119
+ end