bumps 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +5 -0
- data/Manifest.txt +36 -0
- data/README.rdoc +59 -0
- data/Rakefile +29 -0
- data/TODO +20 -0
- data/examples/feature_server +61 -0
- data/features/pull_remote_features.feature +17 -0
- data/features/push_feature_results.feature +11 -0
- data/features/steps/command_output.rb +3 -0
- data/features/steps/cucumber_run.rb +3 -0
- data/features/steps/env.rb +88 -0
- data/features/steps/feature_report.rb +6 -0
- data/features/steps/feature_server.rb +14 -0
- data/features/steps/helpers/scenario_process.rb +67 -0
- data/lib/bumps.rb +25 -0
- data/lib/bumps/configuration.rb +48 -0
- data/lib/bumps/feature.rb +54 -0
- data/lib/bumps/hook_tasks.rb +25 -0
- data/lib/bumps/pre_feature_load_hook.rb +27 -0
- data/lib/bumps/remote_feature.rb +23 -0
- data/lib/bumps/results_push_formatter.rb +37 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/bumps/configuration_spec.rb +78 -0
- data/spec/bumps/feature_spec.rb +119 -0
- data/spec/bumps/hook_tasks_spec.rb +98 -0
- data/spec/bumps/pre_feature_load_hook_spec.rb +44 -0
- data/spec/bumps/remote_feature_spec.rb +70 -0
- data/spec/bumps/results_push_formatter_spec.rb +126 -0
- data/spec/bumps_spec.rb +20 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/rspec.rake +21 -0
- data/test_features/remote_content/destroy_dr_thaddeus_venture.feature +12 -0
- data/test_features/requires/env.rb +3 -0
- metadata +141 -0
@@ -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
|
data/script/console
ADDED
@@ -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"
|
data/script/destroy
ADDED
@@ -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)
|
data/script/generate
ADDED
@@ -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
|