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