bumps 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|