right_develop 1.1.0

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