right_develop 1.1.0
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/.rspec +4 -0
- data/CHANGELOG.rdoc +3 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +92 -0
- data/LICENSE +20 -0
- data/README.rdoc +50 -0
- data/Rakefile +67 -0
- data/VERSION +1 -0
- data/features/cucumber.feature +27 -0
- data/features/rake_integration.feature +27 -0
- data/features/rspec1.feature +67 -0
- data/features/rspec2.feature +67 -0
- data/features/step_definitions/http_client_steps.rb +27 -0
- data/features/step_definitions/request_balancer_steps.rb +93 -0
- data/features/step_definitions/ruby_steps.rb +206 -0
- data/features/step_definitions/serialization_steps.rb +96 -0
- data/features/step_definitions/server_steps.rb +134 -0
- data/features/support/env.rb +84 -0
- data/features/support/file_utils_bundler_mixin.rb +45 -0
- data/lib/right_develop.rb +32 -0
- data/lib/right_develop/ci.rb +40 -0
- data/lib/right_develop/ci/java_cucumber_formatter.rb +60 -0
- data/lib/right_develop/ci/java_spec_formatter.rb +191 -0
- data/lib/right_develop/ci/rake_task.rb +127 -0
- data/lib/right_develop/net.rb +60 -0
- data/lib/right_develop/parsers.rb +10 -0
- data/lib/right_develop/parsers/sax_parser.rb +139 -0
- data/lib/right_develop/parsers/xml_post_parser.rb +113 -0
- data/right_develop.gemspec +140 -0
- data/right_develop.rconf +8 -0
- data/spec/right_develop/parsers/sax_parser_spec.rb +202 -0
- data/spec/spec_helper.rb +28 -0
- metadata +593 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
# Once this file is required, the Rake DSL is loaded - don't do this except inside Rake!!
|
24
|
+
require 'rake/tasklib'
|
25
|
+
|
26
|
+
# Make sure the rest of RightDevelop & CI is required, since this file can be
|
27
|
+
# required directly.
|
28
|
+
require 'right_develop'
|
29
|
+
require 'right_develop/ci'
|
30
|
+
|
31
|
+
# Try to load RSpec 2.x - 1.x Rake tasks
|
32
|
+
['rspec/core/rake_task', 'spec/rake/spectask'].each do |f|
|
33
|
+
begin
|
34
|
+
require f
|
35
|
+
rescue LoadError
|
36
|
+
# no-op, we will raise later
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'cucumber'
|
41
|
+
require 'cucumber/rake/task'
|
42
|
+
|
43
|
+
module RightDevelop::CI
|
44
|
+
# A Rake task definition that creates a CI namespace with appropriate
|
45
|
+
# tests.
|
46
|
+
class RakeTask < ::Rake::TaskLib
|
47
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
48
|
+
|
49
|
+
# The namespace in which to define the continuous integration tasks.
|
50
|
+
#
|
51
|
+
# Default :ci
|
52
|
+
attr_accessor :ci_namespace
|
53
|
+
|
54
|
+
# File glob to select which specs will be run with the spec task.
|
55
|
+
#
|
56
|
+
# Default nil (let RSpec choose pattern)
|
57
|
+
attr_accessor :rspec_pattern
|
58
|
+
|
59
|
+
# Filename (without directory!) to which RSpec XML results should be written.
|
60
|
+
# The CI task will take output_path, append "rspec" as a subdir and finally
|
61
|
+
# append this file name, to come up with a relative path for output. For example:
|
62
|
+
#
|
63
|
+
# Default "rspec.xml"
|
64
|
+
#
|
65
|
+
# output_path = "my_cool_ci"
|
66
|
+
# rspec_output = "my_awesome_rspec.xml"
|
67
|
+
#
|
68
|
+
# Given the options above, the CI harness would write RSpec results to:
|
69
|
+
# my_cool_ci/rspec/my_awesome_rspec.xml
|
70
|
+
attr_accessor :rspec_output
|
71
|
+
|
72
|
+
# The base directory for all output files.
|
73
|
+
#
|
74
|
+
# Default 'measurement'
|
75
|
+
attr_accessor :output_path
|
76
|
+
|
77
|
+
def initialize(*args)
|
78
|
+
@ci_namespace = args.shift || :ci
|
79
|
+
|
80
|
+
yield self if block_given?
|
81
|
+
|
82
|
+
@output_path ||= 'measurement'
|
83
|
+
@rspec_output ||= 'rspec.xml'
|
84
|
+
|
85
|
+
namespace @ci_namespace do
|
86
|
+
task :prep do
|
87
|
+
FileUtils.mkdir_p(@output_path)
|
88
|
+
FileUtils.mkdir_p(File.join(@output_path, 'rspec'))
|
89
|
+
FileUtils.mkdir_p(File.join(@output_path, 'cucumber'))
|
90
|
+
end
|
91
|
+
|
92
|
+
if defined?(::RSpec::Core::RakeTask)
|
93
|
+
# RSpec 2
|
94
|
+
desc "Run RSpec examples"
|
95
|
+
RSpec::Core::RakeTask.new(:spec => :prep) do |t|
|
96
|
+
t.rspec_opts = ['-r', 'right_develop/ci',
|
97
|
+
'-f', JavaSpecFormatter.name,
|
98
|
+
'-o', File.join(@output_path, 'rspec', @rspec_output)]
|
99
|
+
unless self.rspec_pattern.nil?
|
100
|
+
t.pattern = self.rspec_pattern
|
101
|
+
end
|
102
|
+
end
|
103
|
+
elsif defined?(::Spec::Rake::SpecTask)
|
104
|
+
# RSpec 1
|
105
|
+
Spec::Rake::SpecTask.new(:spec => :prep) do |t|
|
106
|
+
desc "Run RSpec Examples"
|
107
|
+
t.spec_opts = ['-r', 'right_develop/ci',
|
108
|
+
'-f', JavaSpecFormatter.name + ":" + File.join(@output_path, 'rspec', @rspec_output)]
|
109
|
+
unless self.rspec_pattern.nil?
|
110
|
+
t.spec_files = FileList[self.rspec_pattern]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise LoadError, "Cannot define CI rake task: unsupported RSpec version"
|
115
|
+
end
|
116
|
+
|
117
|
+
desc "Run Cucumber features"
|
118
|
+
Cucumber::Rake::Task.new do |t|
|
119
|
+
t.cucumber_opts = ['--no-color',
|
120
|
+
'--format', JavaCucumberFormatter.name,
|
121
|
+
'--out', File.join(@output_path, 'cucumber')]
|
122
|
+
end
|
123
|
+
task :cucumber => [:prep]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
# Try to load RSpec 2.x - 1.x
|
24
|
+
['rspec', 'spec'].each do |f|
|
25
|
+
begin
|
26
|
+
require f
|
27
|
+
rescue LoadError
|
28
|
+
# no-op, we will raise later
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module RightDevelop::Net
|
33
|
+
# Extra fatal exceptions to add to RightSupport::Net::RequestBalancer
|
34
|
+
FATAL_TEST_EXCEPTIONS = []
|
35
|
+
|
36
|
+
spec_namespaces = []
|
37
|
+
|
38
|
+
if defined?(::RSpec::Mocks)
|
39
|
+
# RSpec 2.x
|
40
|
+
spec_namespaces += [::RSpec::Mocks, ::RSpec::Expectations]
|
41
|
+
elsif defined?(::Spec::Expectations)
|
42
|
+
# RSpec 1.x
|
43
|
+
spec_namespaces += [::Spec::Expectations]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Use some reflection to locate all RSpec and Test::Unit exceptions
|
47
|
+
spec_namespaces.each do |namespace|
|
48
|
+
namespace.constants.each do |konst|
|
49
|
+
konst = namespace.const_get(konst)
|
50
|
+
if konst.is_a?(Class) && konst.ancestors.include?(Exception)
|
51
|
+
FATAL_TEST_EXCEPTIONS << konst
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
dfe = RightSupport::Net::RequestBalancer::DEFAULT_FATAL_EXCEPTIONS
|
57
|
+
FATAL_TEST_EXCEPTIONS.each do |e|
|
58
|
+
dfe << e unless dfe.include?(e)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Ensure the main gem is required, since this module might be loaded using ruby -r
|
2
|
+
require 'right_develop'
|
3
|
+
|
4
|
+
module RightDevelop
|
5
|
+
module Parsers
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Explicitly require everything else to avoid overreliance on autoload (1-module-deep rule)
|
10
|
+
require 'right_develop/parsers/sax_parser.rb'
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'xml/libxml'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'right_develop/parsers/xml_post_parser.rb'
|
4
|
+
|
5
|
+
module RightDevelop::Parsers
|
6
|
+
class SaxParser
|
7
|
+
extend XmlPostParser
|
8
|
+
|
9
|
+
# Parses XML into a ruby hash
|
10
|
+
#
|
11
|
+
# @param [String] text The XML string to convert into a ruby hash
|
12
|
+
# @param [Hash] opts
|
13
|
+
# @opts [Lambda or Proc] :post_parser A lambda function to run against
|
14
|
+
# the return content of the initial xml parser.
|
15
|
+
# @return [Array or Hash] returns rubified XML in Hash and Array format
|
16
|
+
def self.parse(text, opts = {})
|
17
|
+
# Parse the xml text
|
18
|
+
# http://libxml.rubyforge.org/rdoc/
|
19
|
+
xml = ::XML::SaxParser::string(text)
|
20
|
+
xml.callbacks = new
|
21
|
+
xml.parse
|
22
|
+
|
23
|
+
if opts[:post_parser]
|
24
|
+
if opts[:post_parser].kind_of?(Proc)
|
25
|
+
return opts[:post_parser].call(xml.callbacks.result)
|
26
|
+
else
|
27
|
+
raise ArgumentError.new(":post_parser parameter must be a lambda/proc")
|
28
|
+
end
|
29
|
+
else
|
30
|
+
return xml.callbacks.result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@tag = {}
|
36
|
+
@path = []
|
37
|
+
end
|
38
|
+
|
39
|
+
def result
|
40
|
+
@tag
|
41
|
+
end
|
42
|
+
|
43
|
+
# Callbacks
|
44
|
+
|
45
|
+
def on_error(msg)
|
46
|
+
raise msg
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_start_element_ns(name, attr_hash, prefix, uri, namespaces)
|
50
|
+
# Push parent tag
|
51
|
+
@path << @tag
|
52
|
+
# Create a new tag
|
53
|
+
if @tag[name]
|
54
|
+
@tag[name] = [ @tag[name] ] unless @tag[name].is_a?(Array)
|
55
|
+
@tag[name] << {}
|
56
|
+
@tag = @tag[name].last
|
57
|
+
else
|
58
|
+
@tag[name] = {}
|
59
|
+
@tag = @tag[name]
|
60
|
+
end
|
61
|
+
# Put attributes
|
62
|
+
attr_hash.each do |key, value|
|
63
|
+
@tag["#{key}"] = value
|
64
|
+
end
|
65
|
+
# Put name spaces
|
66
|
+
namespaces.each do |key, value|
|
67
|
+
@tag["@xmlns#{key ? ':'+key.to_s : ''}"] = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_characters(chars)
|
72
|
+
# Ignore lines that contains white spaces only
|
73
|
+
return if chars[/\A\s*\z/m]
|
74
|
+
# Put Text
|
75
|
+
(@tag['@@text'] ||= '') << chars
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_comment(msg)
|
79
|
+
# Put Comments
|
80
|
+
(@tag['@@comment'] ||= '') << msg
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_end_element_ns(name, prefix, uri)
|
84
|
+
# Special handling of empty text fields
|
85
|
+
if @tag.is_a?(Hash) && @tag.empty? && @tag['@@text'].nil?
|
86
|
+
@tag['@@text'] = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Finalize tag's text
|
90
|
+
if @tag.keys.count == 0
|
91
|
+
# Set tag value to nil then the tag is blank
|
92
|
+
name.pluralize == name ? @tag = [] : {}
|
93
|
+
elsif @tag.keys == ['@@text']
|
94
|
+
# Set tag value to string if it has no any other data
|
95
|
+
@tag = @tag['@@text']
|
96
|
+
end
|
97
|
+
# Make sure we saved the changes
|
98
|
+
if @path.last[name].is_a?(Array)
|
99
|
+
# If it is an Array then update the very last item
|
100
|
+
@path.last[name][-1] = @tag
|
101
|
+
else
|
102
|
+
# Otherwise just replace the tag
|
103
|
+
@path.last[name] = @tag
|
104
|
+
end
|
105
|
+
# Pop parent tag
|
106
|
+
@tag = @path.pop
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_start_document
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_reference (name)
|
113
|
+
end
|
114
|
+
|
115
|
+
def on_processing_instruction(target, data)
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_cdata_block(cdata)
|
119
|
+
end
|
120
|
+
|
121
|
+
def on_has_internal_subset()
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_internal_subset (name, external_id, system_id)
|
125
|
+
end
|
126
|
+
|
127
|
+
def on_is_standalone ()
|
128
|
+
end
|
129
|
+
|
130
|
+
def on_has_external_subset ()
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_external_subset (name, external_id, system_id)
|
134
|
+
end
|
135
|
+
|
136
|
+
def on_end_document
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module RightDevelop::Parsers
|
4
|
+
module XmlPostParser
|
5
|
+
|
6
|
+
# Parses a rubified XML hash/array, removing the top level xml tag, along with
|
7
|
+
# any arrays encoded with singular/plural for parent/child nodes.
|
8
|
+
# Intended to allow for one set of code for validating JSON or XML responses.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# Initial XML:
|
12
|
+
# <top_level>
|
13
|
+
# <arrays>
|
14
|
+
# <array item1="one" item2="two"/>
|
15
|
+
# <array item3="three" item4="four"/>
|
16
|
+
# </arrays>
|
17
|
+
# </top_level>
|
18
|
+
#
|
19
|
+
# Before removing nesting, after initial parsing:
|
20
|
+
# {
|
21
|
+
# 'top_level' => {
|
22
|
+
# 'arrays' => {
|
23
|
+
# 'array' => [
|
24
|
+
# {'item1' => 'one', 'item2' => 'two'},
|
25
|
+
# {'item3' => 'three', 'item4' => 'four'}
|
26
|
+
# ]
|
27
|
+
# }
|
28
|
+
# }
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
# After removing nesting:
|
32
|
+
# {
|
33
|
+
# 'arrays' => [
|
34
|
+
# {'item1' => 'one', 'item2' => 'two'},
|
35
|
+
# {'item3' => 'three', 'item4' => 'four'}
|
36
|
+
# ]
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# @param [Array or Hash] xml_object parsed XML object, such as from Parser::Sax.parse.
|
40
|
+
# @return [Array or Hash] returns a ruby Array or Hash with top level xml tags removed,
|
41
|
+
# as well as any extra XML encoded array tags.
|
42
|
+
def self.remove_nesting(xml_object)
|
43
|
+
if xml_object.length != 1 || (!xml_object.is_a?(Hash) && !xml_object.is_a?(Array))
|
44
|
+
raise ArgumentError, "xml_object format doesn't have a single top level entry"
|
45
|
+
end
|
46
|
+
if !xml_object.is_a?(Hash)
|
47
|
+
raise TypeError, "xml_object object doesn't seem to be a Hash or an Array"
|
48
|
+
end
|
49
|
+
xml_object = deep_clone(xml_object)
|
50
|
+
|
51
|
+
#if root & children are the same base word, get rid of both layers
|
52
|
+
root_key = xml_object.keys[0]
|
53
|
+
root_child = xml_object[xml_object.first[0]]
|
54
|
+
if root_child.respond_to?(:keys)
|
55
|
+
root_child_key = root_child.keys[0]
|
56
|
+
else
|
57
|
+
root_child_key = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
#remove root key
|
61
|
+
xml_object = xml_object[xml_object.first[0]]
|
62
|
+
|
63
|
+
#remove extra root child key
|
64
|
+
if root_key.singularize == root_child_key
|
65
|
+
# Ensure object is an array (like JSON responses)
|
66
|
+
xml_object = [xml_object[xml_object.first[0]]].flatten
|
67
|
+
elsif !xml_object
|
68
|
+
# Degenerate case where nothing was contained by parent node
|
69
|
+
# (i.e. no resources were actually returned)
|
70
|
+
xml_object = []
|
71
|
+
end
|
72
|
+
|
73
|
+
remove_nesting_node(xml_object)
|
74
|
+
return xml_object
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Does a deep clone of the passed ruby object. Intended for Arrays / Hashes
|
80
|
+
#
|
81
|
+
# @param [Object] object to deep clone
|
82
|
+
# @return [Object] returns an exact deep copy of the passed in object
|
83
|
+
#
|
84
|
+
# @example BaseHelpers.deep_clone([{:a=>1},{:b=>2}])
|
85
|
+
def self.deep_clone(object)
|
86
|
+
Marshal.load(Marshal.dump(object))
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.remove_nesting_node(xml_object_node)
|
90
|
+
if xml_object_node.is_a?(Array)
|
91
|
+
xml_object_node.each_with_index do |node,index|
|
92
|
+
remove_nesting_node(node)
|
93
|
+
end
|
94
|
+
elsif xml_object_node.is_a?(Hash)
|
95
|
+
# If child is singular version of parent, remove extra child pointer
|
96
|
+
xml_object_node.each do |parent_key, parent_value|
|
97
|
+
if parent_value.is_a?(Hash) && parent_value.length == 1
|
98
|
+
child_key = parent_value.keys[0]
|
99
|
+
if parent_key.singularize == child_key
|
100
|
+
# Wrap xml object in an array so it matches JSON format
|
101
|
+
child_node = xml_object_node[parent_key][child_key]
|
102
|
+
if child_node.is_a?(Hash)
|
103
|
+
child_node = [child_node]
|
104
|
+
end
|
105
|
+
xml_object_node[parent_key] = child_node
|
106
|
+
end
|
107
|
+
end
|
108
|
+
remove_nesting_node(parent_value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|