gorp 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/Manifest +7 -0
  2. data/README +41 -0
  3. data/Rakefile +31 -0
  4. data/gorp.gemspec +61 -0
  5. data/lib/gorp/test.rb +182 -0
  6. data/lib/gorp.rb +705 -0
  7. data/lib/version.rb +9 -0
  8. metadata +148 -0
data/Manifest ADDED
@@ -0,0 +1,7 @@
1
+ README
2
+ Rakefile
3
+ Manifest
4
+ gorp.gemspec
5
+ lib/version.rb
6
+ lib/gorp.rb
7
+ lib/gorp/test.rb
data/README ADDED
@@ -0,0 +1,41 @@
1
+ Running this should only require Rails 2.2.2 or later, and the command line
2
+ interfaces for sqlite3 and curl. Tested on Ubuntu Linux 9.04 ("Jaunty
3
+ Jackalope") and Mac OSX 10.5.6 ("Leopard").
4
+
5
+ Installation of all necessary dependencies from a fresh install of Ubuntu 9.04:
6
+
7
+ sudo apt-get install rails git-core sqlite3 curl
8
+ sudo gem install rubygems-update
9
+ sudo /var/lib/gems/1.8/bin/update_rubygems
10
+ sudo gem install rails
11
+ sudo gem sources -a http://gems.github.com
12
+ sudo gem install rubys-gorp
13
+
14
+ Execution instructions:
15
+
16
+ This is a library which, among other things, will interpret ARGV. Here's
17
+ an example based on http://github.com/rubys/awdwr:
18
+
19
+ ruby makedepot.rb [VERSION] [restore] [RANGE]... [save]
20
+
21
+ "restore" - restore from snapshot before resuming execution
22
+
23
+ "VERSION" specifies the Rails version to test. Examples:
24
+ edge
25
+ _2.2.2_
26
+ ~/git
27
+
28
+ "RANGE" specifies a set of sections to execute. Examples:
29
+ 6.2..6.5
30
+ 7.1-9.5
31
+ 16
32
+
33
+ "save" - save snapshot after execution completes
34
+
35
+ Tests against the output produced (e.g., makedepot.html) can also be run
36
+ separately:
37
+
38
+ ruby checkdepot.rb [partial]
39
+
40
+ "partial" means that tests which cover sections that are not included in
41
+ makedepot.html are to be omitted.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ require File.expand_path(File.dirname(__FILE__) + "/lib/version")
6
+
7
+ Echoe.new('gorp', Gorp::VERSION::STRING) do |p|
8
+ p.summary = "Rails scenario testing support library"
9
+ p.description = <<-EOF
10
+ Enables the creation of scenarios that involve creating a rails project,
11
+ starting and stoppping of servers, generating projects, editing files,
12
+ issuing http requests, running of commands, etc. Output is captured as
13
+ a single HTML file that can be viewed locally or uploaded.
14
+
15
+ Additionally, there is support for verification, in the form of defining
16
+ assertions based on selections (typically CSS) against the generated HTML.
17
+ EOF
18
+ p.url = "http://github.com/rubys/gorp"
19
+ p.author = "Sam Ruby"
20
+ p.email = "rubys@intertwingly.net"
21
+ p.dependencies = %w(
22
+ arel
23
+ builder
24
+ erubis
25
+ rack
26
+ rack-test
27
+ rake
28
+ sqlite3-ruby
29
+ tzinfo
30
+ )
31
+ end
data/gorp.gemspec ADDED
@@ -0,0 +1,61 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{gorp}
5
+ s.version = "0.7.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sam Ruby"]
9
+ s.date = %q{2009-10-25}
10
+ s.description = %q{ Enables the creation of scenarios that involve creating a rails project,
11
+ starting and stoppping of servers, generating projects, editing files,
12
+ issuing http requests, running of commands, etc. Output is captured as
13
+ a single HTML file that can be viewed locally or uploaded.
14
+
15
+ Additionally, there is support for verification, in the form of defining
16
+ assertions based on selections (typically CSS) against the generated HTML.
17
+ }
18
+ s.email = %q{rubys@intertwingly.net}
19
+ s.extra_rdoc_files = ["README", "lib/version.rb", "lib/gorp.rb", "lib/gorp/test.rb"]
20
+ s.files = ["README", "Rakefile", "Manifest", "gorp.gemspec", "lib/version.rb", "lib/gorp.rb", "lib/gorp/test.rb"]
21
+ s.homepage = %q{http://github.com/rubys/gorp}
22
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Gorp", "--main", "README"]
23
+ s.require_paths = ["lib"]
24
+ s.rubyforge_project = %q{gorp}
25
+ s.rubygems_version = %q{1.3.3}
26
+ s.summary = %q{Rails scenario testing support library}
27
+
28
+ if s.respond_to? :specification_version then
29
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
30
+ s.specification_version = 3
31
+
32
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
33
+ s.add_runtime_dependency(%q<arel>, [">= 0"])
34
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
35
+ s.add_runtime_dependency(%q<erubis>, [">= 0"])
36
+ s.add_runtime_dependency(%q<rack>, [">= 0"])
37
+ s.add_runtime_dependency(%q<rack-test>, [">= 0"])
38
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
39
+ s.add_runtime_dependency(%q<sqlite3-ruby>, [">= 0"])
40
+ s.add_runtime_dependency(%q<tzinfo>, [">= 0"])
41
+ else
42
+ s.add_dependency(%q<arel>, [">= 0"])
43
+ s.add_dependency(%q<builder>, [">= 0"])
44
+ s.add_dependency(%q<erubis>, [">= 0"])
45
+ s.add_dependency(%q<rack>, [">= 0"])
46
+ s.add_dependency(%q<rack-test>, [">= 0"])
47
+ s.add_dependency(%q<rake>, [">= 0"])
48
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
49
+ s.add_dependency(%q<tzinfo>, [">= 0"])
50
+ end
51
+ else
52
+ s.add_dependency(%q<arel>, [">= 0"])
53
+ s.add_dependency(%q<builder>, [">= 0"])
54
+ s.add_dependency(%q<erubis>, [">= 0"])
55
+ s.add_dependency(%q<rack>, [">= 0"])
56
+ s.add_dependency(%q<rack-test>, [">= 0"])
57
+ s.add_dependency(%q<rake>, [">= 0"])
58
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
59
+ s.add_dependency(%q<tzinfo>, [">= 0"])
60
+ end
61
+ end
data/lib/gorp/test.rb ADDED
@@ -0,0 +1,182 @@
1
+ require 'test/unit'
2
+ require 'builder'
3
+
4
+ begin
5
+ # installed Rails (2.3.3 ish)
6
+ require 'active_support'
7
+ $:.unshift 'work/depot/vendor/rails/activesupport/lib'
8
+ require 'active_support/version'
9
+ $:.shift
10
+ rescue LoadError
11
+ # testing Rails (3.0 ish)
12
+ $:.unshift 'work/depot/vendor/rails/activesupport/lib'
13
+ require 'active_support'
14
+ require 'active_support/version'
15
+ end
16
+
17
+ require 'active_support/test_case'
18
+
19
+ module Book
20
+ end
21
+
22
+ class Book::TestCase < ActiveSupport::TestCase
23
+ # just enough infrastructure to get 'assert_select' to work
24
+ begin
25
+ # installed Rails (2.3.3 ish)
26
+ require 'action_controller'
27
+ require 'action_controller/assertions/selector_assertions'
28
+ include ActionController::Assertions::SelectorAssertions
29
+ rescue LoadError
30
+ # testing Rails (3.0 ish)
31
+ $:.unshift 'work/depot/vendor/rails/actionpack/lib'
32
+ require 'action_controller'
33
+ require 'action_dispatch/testing/assertions'
34
+ require 'action_dispatch/testing/assertions/selector'
35
+ include ActionDispatch::Assertions::SelectorAssertions
36
+ $:.shift
37
+ end
38
+
39
+ # micro DSL allowing the definition of optional tests
40
+ def self.section number, title, &tests
41
+ number = (sprintf "%f", number).sub(/0+$/,'') if number.kind_of? Float
42
+ return if ARGV.include? 'partial' and !@@sections.has_key? number.to_s
43
+ test "#{number} #{title}" do
44
+ instance_eval {select number}
45
+ instance_eval &tests
46
+ end
47
+ end
48
+
49
+ # read and pre-process $input.html (only done once, and cached)
50
+ def self.input filename
51
+ # read $input output; remove front matter and footer
52
+ input = open("#{filename}.html").read
53
+ head, body, tail = input.split /<body>\s+|\s+<\/body>/m
54
+
55
+ # split into sections
56
+ @@sections = body.split(/<a class="toc" id="section-(.*?)">/)
57
+ @@sections[-1], env = @@sections.last.split(/<a class="toc" id="env">/)
58
+ env, todos = env.split(/<a class="toc" id="todos">/)
59
+
60
+ # convert to a Hash
61
+ @@sections = Hash[*@@sections.unshift(:contents)]
62
+ @@sections[:head] = head
63
+ @@sections[:todos] = todos
64
+ @@sections[:env] = env
65
+ @@sections[:tail] = tail
66
+
67
+ # reattach anchors
68
+ @@sections.each do |key,value|
69
+ next unless key =~ /^\d/
70
+ @@sections[key] = "<a class=\"toc\" name=\"section-#{key}\">#{value}"
71
+ end
72
+
73
+ # report version
74
+ body =~ /rails .*?-v<\/pre>\s+.*?>(.*)<\/pre>/
75
+ @@version = $1
76
+ @@version += ' (git)' if body =~ /ln -s.*vendor.rails/
77
+ @@version += ' (edge)' if body =~ /rails:freeze:edge/
78
+ STDERR.puts @@version
79
+ end
80
+
81
+ def self.output filename
82
+ $output = filename
83
+ at_exit { HTMLRunner.run(self) }
84
+ end
85
+
86
+ # select an individual section from the HTML
87
+ def select number
88
+ raise "Section #{number} not found" unless @@sections.has_key? number.to_s
89
+ @selected = HTML::Document.new(@@sections[number.to_s]).root.children
90
+ assert @@sections[number.to_s] !~
91
+ /<pre class="traceback">\s+#&lt;IndexError: regexp not matched&gt;/,
92
+ "edit failed"
93
+ end
94
+
95
+ def collect_stdout
96
+ css_select('.stdout').map do |tag|
97
+ tag.children.join.gsub('&lt;','<').gsub('&gt;','>')
98
+ end
99
+ end
100
+
101
+ def sort_hash line
102
+ line.sub(/^(=> )?\{.*\}$/) do |match|
103
+ "#{$1}{#{match.scan(/:?"?\w+"?=>[^\[].*?(?=, |\})/).sort.join(', ')}}"
104
+ end
105
+ end
106
+
107
+ def self.sections
108
+ @@sections
109
+ end
110
+ end
111
+
112
+ # insert failure indicators into #{output}.html
113
+ require 'test/unit/ui/console/testrunner'
114
+ class HTMLRunner < Test::Unit::UI::Console::TestRunner
115
+ def self.run suite
116
+ @@sections = suite.sections
117
+ super
118
+ end
119
+
120
+ def attach_to_mediator
121
+ super
122
+ @html_tests = []
123
+ @mediator.add_listener(Test::Unit::TestResult::FAULT,
124
+ &method(:html_fault))
125
+ @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED,
126
+ &method(:html_summary))
127
+ end
128
+
129
+ def html_fault fault
130
+ if fault.test_name =~ /^test_([\d.]+)_.*\(\w+\)$/
131
+ name = $1
132
+ sections = @@sections
133
+ return unless sections.has_key? name
134
+
135
+ # indicate failure in the toc
136
+ sections[:contents][/<a href="#section-#{name}"()>/,1] =
137
+ ' style="color:red; font-weight:bold"'
138
+
139
+ # provide details in the section itself
140
+ x = Builder::XmlMarkup.new(:indent => 2)
141
+ if fault.respond_to? :location
142
+ x.pre fault.message.sub(".\n<false> is not true",'') +
143
+ "\n\nTraceback:\n " + fault.location.join("\n "),
144
+ :class=>'traceback'
145
+ else
146
+ x.pre fault.message, :class=>'traceback'
147
+ end
148
+ sections[name][/<\/a>()/,1] = x.target!
149
+
150
+ # add to the todos
151
+ x = Builder::XmlMarkup.new(:indent => 2)
152
+ x.li do
153
+ x.a "Section #{name}", :href => "#section-#{name}"
154
+ x.tt fault.message.sub(".\n<false> is not true",'').
155
+ sub(/ but was\n.*/, '.')
156
+ end
157
+ sections[:todos][/() *<\/ul>/,1] = x.target!.gsub(/^/,' ')
158
+ end
159
+ end
160
+
161
+ def html_summary elapsed
162
+ open("#{$output}.html",'w') do |output|
163
+ sections = @@sections
164
+ output.write(sections.delete(:head))
165
+ output.write("<body>\n ")
166
+ output.write(sections.delete(:contents))
167
+ env = sections.delete(:env)
168
+ todos = sections.delete(:todos)
169
+ tail = sections.delete(:tail)
170
+ sections.keys.sort_by {|key| key.split('.').map {|n| n.to_i}}.each do |n|
171
+ output.write(sections[n])
172
+ end
173
+ output.write('<a class="toc" id="env">')
174
+ output.write(env)
175
+ output.write('<a class="toc" id="todos">')
176
+ todos.sub! /<ul.*\/ul>/m, '<h2>None!</h2>' unless todos.include? '<li>'
177
+ output.write(todos)
178
+ output.write("\n </body>")
179
+ output.write(tail)
180
+ end
181
+ end
182
+ end
data/lib/gorp.rb ADDED
@@ -0,0 +1,705 @@
1
+ # The following is under the "I ain't too proud" school of programming.
2
+ # global variables, repetition, and brute force abounds.
3
+ #
4
+ # You have been warned.
5
+
6
+ require 'fileutils'
7
+ require 'open3'
8
+ require 'net/http'
9
+ require 'builder'
10
+ require 'stringio'
11
+
12
+ require 'rbconfig'
13
+ $ruby = File.join(Config::CONFIG["bindir"], Config::CONFIG["RUBY_INSTALL_NAME"])
14
+
15
+ # Micro DSL for declaring an ordered set of book sections
16
+ $sections = []
17
+ def section number, title, &steps
18
+ number = (sprintf "%f", number).sub(/0+$/,'') if number.kind_of? Float
19
+ $sections << [number, title, steps]
20
+ end
21
+
22
+ # verify that port is available for testing
23
+ if (Net::HTTP.get_response('localhost','/',3000).code == '200' rescue false)
24
+ STDERR.puts 'local server already running on port 3000'
25
+ exit
26
+ end
27
+
28
+ $BASE=File.expand_path(File.dirname(caller.last.split(':').first)) unless $BASE
29
+ $WORK = File.join($BASE,'work')
30
+ $DATA = File.join($BASE,'data')
31
+ $CODE = File.join($DATA,'code')
32
+ $x = Builder::XmlMarkup.new(:indent => 2)
33
+ $toc = Builder::XmlMarkup.new(:indent => 2)
34
+ $todos = Builder::XmlMarkup.new(:indent => 2)
35
+ $issue = 0
36
+ $style = Builder::XmlMarkup.new(:indent => 2)
37
+
38
+ FileUtils.mkdir_p $WORK
39
+
40
+ class String
41
+ def unindent(n)
42
+ gsub Regexp.new("^#{' '*n}"), ''
43
+ end
44
+ end
45
+
46
+ def read name
47
+ open(File.join($DATA, name)) {|file| file.read}
48
+ end
49
+
50
+ def overview message
51
+ $x.p message.gsub(/(^|\n)\s+/, ' ').strip, :class=>'overview'
52
+ end
53
+
54
+ def desc message
55
+ $x.p message, :class=>'desc'
56
+ end
57
+
58
+ def log type, message
59
+ type = type.to_s.ljust(5).upcase
60
+ STDOUT.puts Time.now.strftime("[%Y-%m-%d %H:%M:%S] #{type} #{message}")
61
+ end
62
+
63
+ def head number, title
64
+ $section = "#{number} #{title}"
65
+ log '====>', $section
66
+
67
+ $x.a(:class => 'toc', :id => "section-#{number}") {$x.h2 $section}
68
+ $toc.li {$toc.a $section, :href => "#section-#{number}"}
69
+ end
70
+
71
+ def issue text, options={}
72
+ log :issue, text
73
+
74
+ $issue+=1
75
+ $x.p :class => 'issue', :id => "issue-#{$issue}" do
76
+ $x.text! text
77
+ if options[:ticket]
78
+ $x.text! ' ('
79
+ $x.a "ticket #{options[:ticket]}", :href=>
80
+ 'https://rails.lighthouseapp.com/projects/8994/tickets/' +
81
+ options[:ticket].to_s
82
+ $x.text! ')'
83
+ end
84
+ end
85
+ $todos.li do
86
+ section = $section.split(' ').first
87
+ $todos.a "Section #{section}:", :href => "#section-#{$section}"
88
+ $todos.a "#{text}", :href => "#issue-#{$issue}"
89
+ end
90
+ end
91
+
92
+ def db statement, hilight=[]
93
+ log :db, statement
94
+ $x.pre "sqlite3> #{statement}", :class=>'stdin'
95
+ cmd = "sqlite3 --line db/development.sqlite3 #{statement.inspect}"
96
+ popen3 cmd, hilight
97
+ end
98
+
99
+ def ruby args
100
+ cmd "ruby #{args}"
101
+ end
102
+
103
+ def console script
104
+ cmd "echo #{script.inspect} | ruby script/console '--irb=irb -f'"
105
+ end
106
+
107
+ def cmd args, hilight=[]
108
+ log :cmd, args
109
+ $x.pre args, :class=>'stdin'
110
+ if args == 'rake db:migrate'
111
+ Dir.chdir 'db/migrate' do
112
+ date = '20100301000000'
113
+ Dir['[0-9]*'].sort_by {|file| file=~/201003/?file:'x'+file}.each do |file|
114
+ file =~ /^([0-9]*)_(.*)$/
115
+ FileUtils.mv file, "#{date}_#{$2}" unless $1 == date.next!
116
+ $x.pre "mv #{file} #{date}_#{$2}" unless $1 == date
117
+ end
118
+ end
119
+ end
120
+ args += ' -C' if args == 'ls -p'
121
+ popen3 args, hilight
122
+ end
123
+
124
+ def popen3 args, hilight=[]
125
+ Open3.popen3(args) do |pin, pout, perr|
126
+ terr = Thread.new do
127
+ $x.pre perr.readline.chomp, :class=>'stderr' until perr.eof?
128
+ end
129
+ pin.close
130
+ until pout.eof?
131
+ line = pout.readline
132
+ if hilight.any? {|pattern| line.include? pattern}
133
+ outclass='hilight'
134
+ elsif line =~ /\x1b\[\d/
135
+ line.gsub! /\x1b\[1m\x1b\[3\dm(.*?)\x1b\[0m/, '\1'
136
+ outclass = 'logger'
137
+ else
138
+ outclass='stdout'
139
+ end
140
+
141
+ if line.strip.size == 0
142
+ $x.pre ' ', :class=>outclass
143
+ else
144
+ $x.pre line.chomp, :class=>outclass
145
+ end
146
+ end
147
+ terr.join
148
+ end
149
+ end
150
+
151
+ def irb file
152
+ $x.pre "irb #{file}", :class=>'stdin'
153
+ log :irb, file
154
+ cmd = "irb -f -rubygems -r ./config/boot --prompt-mode simple #{$CODE}/#{file}"
155
+ Open3.popen3(cmd) do |pin, pout, perr|
156
+ terr = Thread.new do
157
+ until perr.eof?
158
+ line = perr.readline.chomp
159
+ line.gsub! /\x1b\[4(;\d+)*m(.*?)\x1b\[0m/, '\2'
160
+ line.gsub! /\x1b\[0(;\d+)*m(.*?)\x1b\[0m/, '\2'
161
+ line.gsub! /\x1b\[0(;\d+)*m/, ''
162
+ $x.pre line, :class=>'stderr'
163
+ end
164
+ end
165
+ pin.close
166
+ prompt = nil
167
+ until pout.eof?
168
+ line = pout.readline
169
+ if line =~ /^([?>]>)\s*#\s*(START|END):/
170
+ prompt = $1
171
+ elsif line =~ /^([?>]>)\s+$/
172
+ $x.pre ' ', :class=>'irb'
173
+ prompt ||= $1
174
+ elsif line =~ /^([?>]>)(.*)\n/
175
+ prompt ||= $1
176
+ $x.pre prompt + $2, :class=>'irb'
177
+ prompt = nil
178
+ elsif line =~ /^\w+(::\w+)*: /
179
+ $x.pre line.chomp, :class=>'stderr'
180
+ elsif line =~ /^\s+from [\/.:].*:\d+:in `\w.*'\s*$/
181
+ $x.pre line.chomp, :class=>'stderr'
182
+ else
183
+ $x.pre line.chomp, :class=>'stdout'
184
+ end
185
+ end
186
+ terr.join
187
+ end
188
+ end
189
+
190
+ def edit filename, tag=nil
191
+ log :edit, filename
192
+ $x.pre "edit #{filename}", :class=>'stdin'
193
+
194
+ stale = File.mtime(filename) rescue Time.now-2
195
+ data = open(filename) {|file| file.read} rescue ''
196
+ before = data.split("\n")
197
+
198
+ begin
199
+ yield data
200
+ open(filename,'w') {|file| file.write data}
201
+ File.utime(stale+2, stale+2, filename) if File.mtime(filename) <= stale
202
+
203
+ rescue Exception => e
204
+ $x.pre :class => 'traceback' do
205
+ STDERR.puts e.inspect
206
+ $x.text! "#{e.inspect}\n"
207
+ e.backtrace.each {|line| $x.text! " #{line}\n"}
208
+ end
209
+ tag = nil
210
+
211
+ ensure
212
+ include = tag.nil?
213
+ hilight = false
214
+ data.split("\n").each do |line|
215
+ if line =~ /START:(\w+)/
216
+ include = true if $1 == tag
217
+ elsif line =~ /END:(\w+)/
218
+ include = false if $1 == tag
219
+ elsif line =~ /START_HIGHLIGHT/
220
+ hilight = true
221
+ elsif line =~ /END_HIGHLIGHT/
222
+ hilight = false
223
+ elsif include
224
+ if hilight or ! before.include?(line)
225
+ outclass='hilight'
226
+ else
227
+ outclass='stdout'
228
+ end
229
+
230
+ if line.empty?
231
+ $x.pre ' ', :class=>outclass
232
+ else
233
+ $x.pre line, :class=>outclass
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ # pluggable XML parser support
241
+ begin
242
+ raise LoadError if ARGV.include? 'rexml'
243
+ require 'nokogiri'
244
+ def xhtmlparse(text)
245
+ Nokogiri::HTML(text)
246
+ end
247
+ Comment=Nokogiri::XML::Comment
248
+ rescue LoadError
249
+ require 'rexml/document'
250
+
251
+ HTML_VOIDS = %w(area base br col command embed hr img input keygen link meta
252
+ param source)
253
+
254
+ def xhtmlparse(text)
255
+ begin
256
+ require 'htmlentities'
257
+ text.gsub! '&amp;', '&amp;amp;'
258
+ text.gsub! '&lt;', '&amp;lt;'
259
+ text.gsub! '&gt;', '&amp;gt;'
260
+ text.gsub! '&apos;', '&amp;apos;'
261
+ text.gsub! '&quot;', '&amp;quot;'
262
+ text.force_encoding('utf-8') if text.respond_to? :force_encoding
263
+ text = HTMLEntities.new.decode(text)
264
+ rescue LoadError
265
+ end
266
+ doc = REXML::Document.new(text)
267
+ doc.get_elements('//*[not(* or text())]').each do |e|
268
+ e.text='' unless HTML_VOIDS.include? e.name
269
+ end
270
+ doc
271
+ end
272
+
273
+ class REXML::Element
274
+ def has_attribute? name
275
+ self.attributes.has_key? name
276
+ end
277
+
278
+ def at xpath
279
+ self.elements[xpath]
280
+ end
281
+
282
+ def search xpath
283
+ self.elements.to_a(xpath)
284
+ end
285
+
286
+ def content=(string)
287
+ self.text=string
288
+ end
289
+
290
+ def [](index)
291
+ if index.instance_of? String
292
+ self.attributes[index]
293
+ else
294
+ super(index)
295
+ end
296
+ end
297
+
298
+ def []=(index, value)
299
+ if index.instance_of? String
300
+ self.attributes[index] = value
301
+ else
302
+ super(index, value)
303
+ end
304
+ end
305
+ end
306
+
307
+ module REXML::Node
308
+ def before(node)
309
+ self.parent.insert_before(self, node)
310
+ end
311
+
312
+ def add_previous_sibling(node)
313
+ self.parent.insert_before(self, node)
314
+ end
315
+
316
+ def serialize
317
+ self.to_s
318
+ end
319
+ end
320
+
321
+ # monkey patch for Ruby 1.8.6
322
+ doc = REXML::Document.new '<doc xmlns="ns"><item name="foo"/></doc>'
323
+ if not doc.root.elements["item[@name='foo']"]
324
+ class REXML::Element
325
+ def attribute( name, namespace=nil )
326
+ prefix = nil
327
+ prefix = namespaces.index(namespace) if namespace
328
+ prefix = nil if prefix == 'xmlns'
329
+ attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
330
+ end
331
+ end
332
+ end
333
+
334
+ Comment = REXML::Comment
335
+ end
336
+
337
+ def snap response, form={}
338
+ if response.content_type == 'text/plain' or response.content_type =~ /xml/
339
+ $x.div :class => 'body' do
340
+ response.body.split("\n").each do |line|
341
+ $x.pre line.chomp, :class=>'stdout'
342
+ end
343
+ end
344
+ return
345
+ end
346
+
347
+ if response.body =~ /<body/
348
+ body = response.body
349
+ else
350
+ body = "<body>#{response.body}</body>"
351
+ end
352
+
353
+ begin
354
+ doc = xhtmlparse(body)
355
+ rescue
356
+ body.split("\n").each {|line| $x.pre line.chomp, :class=>'hilight'}
357
+ raise
358
+ end
359
+
360
+ title = doc.at('html/head/title').text rescue ''
361
+ body = doc.at('//body')
362
+ doc.search('//link[@rel="stylesheet"]').each do |sheet|
363
+ body.children.first.add_previous_sibling(sheet)
364
+ end
365
+
366
+ if ! form.empty?
367
+ body.search('//input[@name]').each do |input|
368
+ input['value'] ||= form[input['name']].to_s
369
+ end
370
+ body.search('//textarea[@name]').each do |textarea|
371
+ textarea.text ||= form[textarea['name']].to_s
372
+ end
373
+ end
374
+
375
+ %w{ a[@href] form[@action] }.each do |xpath|
376
+ name = xpath[/@(\w+)/,1]
377
+ body.search("//#{xpath}").each do |element|
378
+ next if element[name] =~ /^http:\/\//
379
+ element[name] = URI.join('http://localhost:3000/', element[name]).to_s
380
+ end
381
+ end
382
+
383
+ %w{ img[@src] }.each do |xpath|
384
+ name = xpath[/@(\w+)/,1]
385
+ body.search("//#{xpath}").each do |element|
386
+ if element[name][0] == ?/
387
+ element[name] = 'data' + element[name]
388
+ end
389
+ end
390
+ end
391
+
392
+ body.search('//textarea').each do |element|
393
+ element.content=''
394
+ end
395
+
396
+ attrs = {:class => 'body', :title => title}
397
+ attrs[:class] = 'traceback' if response.code == '500'
398
+ attrs[:id] = body['id'] if body['id']
399
+ $x.div(attrs) do
400
+ body.children.each do |child|
401
+ $x << child.serialize unless child.instance_of?(Comment)
402
+ end
403
+ end
404
+ $x.div :style => "clear: both"
405
+ end
406
+
407
+ def get path
408
+ post path, {}
409
+ end
410
+
411
+ def post path, form
412
+ $x.pre "get #{path}", :class=>'stdin'
413
+
414
+ if path.include? ':'
415
+ host, port, path = URI.parse(path).select(:host, :port, :path)
416
+ else
417
+ host, port = '127.0.0.1', 3000
418
+ end
419
+
420
+ Net::HTTP.start(host, port) do |http|
421
+ get = Net::HTTP::Get.new(path)
422
+ get['Cookie'] = $COOKIE if $COOKIE
423
+ response = http.request(get)
424
+ snap response, form
425
+ $COOKIE = response.response['set-cookie'] if response.response['set-cookie']
426
+
427
+ if ! form.empty?
428
+ body = xhtmlparse(response.body).at('//body')
429
+ body = xhtmlparse(response.body).root unless body
430
+ xform = body.at('//form[.//input[@name="commit"]]')
431
+ return unless xform
432
+ path = xform.attribute('action').to_s unless
433
+ xform.attribute('action').to_s.empty?
434
+ $x.pre "post #{path}", :class=>'stdin'
435
+
436
+ $x.ul do
437
+ form.each do |name, value|
438
+ $x.li "#{name} => #{value}"
439
+ end
440
+ end
441
+
442
+ body.search('//input[@type="hidden"]').each do |element|
443
+ form[element['name']] ||= element['value']
444
+ end
445
+
446
+ post = Net::HTTP::Post.new(path)
447
+ post.form_data = form
448
+ post['Cookie'] = $COOKIE
449
+ response=http.request(post)
450
+ snap response
451
+ end
452
+
453
+ if response.code == '302'
454
+ $COOKIE=response.response['set-cookie'] if response.response['set-cookie']
455
+ path = response['Location']
456
+ $x.pre "get #{path}", :class=>'stdin'
457
+ get = Net::HTTP::Get.new(path)
458
+ get['Cookie'] = $COOKIE if $COOKIE
459
+ response = http.request(get)
460
+ snap response
461
+ end
462
+ end
463
+ end
464
+
465
+ # select a version of Rails
466
+ if ARGV.first =~ /^_\d[.\d]*_$/
467
+ $rails = "rails #{ARGV.first}"
468
+ elsif File.directory?(ARGV.first.to_s)
469
+ $rails = ARGV.first
470
+ $rails = File.join($rails,'rails') if
471
+ File.directory?(File.join($rails,'rails'))
472
+ $rails = File.expand_path($rails)
473
+ else
474
+ $rails = 'rails'
475
+ end
476
+
477
+ def which_rails rails
478
+ railties = File.join(rails, 'railties', 'bin', 'rails')
479
+ rails = railties if File.exists?(railties)
480
+ if File.exists?(rails)
481
+ firstline = open(rails) {|file| file.readlines.first}
482
+ rails = 'ruby ' + rails unless firstline =~ /^#!/
483
+ end
484
+ rails
485
+ end
486
+
487
+ def rails name, app=nil
488
+ Dir.chdir($WORK)
489
+ FileUtils.rm_rf name
490
+ log :rails, name
491
+
492
+ # determine how to invoke rails
493
+ rails = which_rails $rails
494
+
495
+ $x.pre "#{rails} #{name}", :class=>'stdin'
496
+ popen3 "#{rails} #{name}"
497
+
498
+ # make paths seem Mac OSX'ish
499
+ Dir["#{name}/public/dispatch.*"].each do |dispatch|
500
+ code = open(dispatch) {|file| file.read}
501
+ code.sub! /^#!.*/, '#!/opt/local/bin/ruby'
502
+ open(dispatch,'w') {|file| file.write code}
503
+ end
504
+
505
+ Dir.chdir(name)
506
+ FileUtils.rm_rf 'public/.htaccess'
507
+
508
+ cmd 'rake rails:freeze:edge' if ARGV.include? 'edge'
509
+
510
+ if $rails != 'rails' and File.directory?($rails)
511
+ cmd "ln -s #{$rails} vendor/rails"
512
+ end
513
+ end
514
+
515
+ def restart_server
516
+ log :server, 'restart'
517
+ if $server
518
+ $x.h3 'Restart the server.'
519
+ Process.kill "INT", $server
520
+ Process.wait($server)
521
+ else
522
+ $x.h3 'Start the server.'
523
+ end
524
+
525
+ $server = fork
526
+ if $server
527
+ # wait for server to start
528
+ 60.times do
529
+ sleep 0.5
530
+ begin
531
+ status = Net::HTTP.get_response('localhost','/',3000).code
532
+ break if %(200 404).include? status
533
+ rescue Errno::ECONNREFUSED
534
+ end
535
+ end
536
+ else
537
+ begin
538
+ if File.exist?('config.ru')
539
+ require 'rack'
540
+ server = Rack::Builder.new {eval open('config.ru').read}
541
+ Rack::Handler::WEBrick.run(server, :Port => 3000)
542
+ else
543
+ # start server, redirecting stdout to a string
544
+ $stdout = StringIO.open('','w')
545
+ require './config/boot'
546
+ if Rails::VERSION::MAJOR == 2
547
+ require 'commands/server'
548
+ else
549
+ require 'rails/commands/server'
550
+ end
551
+ end
552
+ rescue
553
+ STDERR.puts $!
554
+ $!.backtrace.each {|method| STDERR.puts "\tfrom " + method}
555
+ ensure
556
+ Process.exit!
557
+ end
558
+ end
559
+ end
560
+
561
+ def secsplit section
562
+ section.to_s.split('.').map {|n| n.to_i}
563
+ end
564
+
565
+ at_exit do
566
+ $x.html :xmlns => 'http://www.w3.org/1999/xhtml' do
567
+ $x.header do
568
+ $x.title $title
569
+ $x.meta 'http-equiv'=>'text/html; charset=UTF-8'
570
+ $x.style :type => "text/css" do
571
+ $x.text! <<-'EOF'.unindent(2)
572
+ body {background-color: #F5F5DC}
573
+ #banner {margin-top: 0}
574
+ pre {font-weight: bold; margin: 0; padding: 0}
575
+ pre.stdin {color: #800080; margin-top: 1em; padding: 0}
576
+ pre.irb {color: #800080; padding: 0}
577
+ pre.stdout {color: #000; padding: 0}
578
+ pre.logger {color: #088; padding: 0}
579
+ pre.hilight {color: #000; background-color: #FF0; padding: 0}
580
+ pre.stderr {color: #F00; padding: 0}
581
+ div.body {border-style: solid; border-color: #800080; padding: 0.5em}
582
+ .issue, .traceback {background:#FDD; border: 4px solid #F00;
583
+ font-weight: bold; margin-top: 1em; padding: 0.5em}
584
+ div.body, .issue, .traceback {
585
+ -webkit-border-radius: 0.7em; -moz-border-radius: 0.7em;}
586
+ ul.toc {list-style: none}
587
+ ul a {text-decoration: none}
588
+ ul a:hover {text-decoration: underline; color: #000;
589
+ background-color: #F5F5DC}
590
+ a.toc h2 {background-color: #981A21; color:#FFF; padding: 6px}
591
+ ul a:visited {color: #000}
592
+ h2 {clear: both}
593
+ p.desc {font-style: italic}
594
+ p.overview {border-width: 2px; border-color: #000;
595
+ border-style: solid; border-radius: 4em;
596
+ background-color: #CCF; margin: 1.5em 1.5em; padding: 1em 2em;
597
+ -webkit-border-radius: 4em; -moz-border-radius: 4em;}
598
+ EOF
599
+ end
600
+ end
601
+
602
+ $x.body do
603
+ $x.h1 $title, :id=>'banner'
604
+ $x.h2 'Table of Contents'
605
+ $x.ul :class => 'toc'
606
+
607
+ # determine which range(s) of steps are to be executed
608
+ ranges = ARGV.grep(/^ \d+(.\d+)? ( (-|\.\.) \d+(.\d+)? )? /x).map do |arg|
609
+ bounds = arg.split(/-|\.\./)
610
+ Range.new(secsplit(bounds.first), secsplit(bounds.last))
611
+ end
612
+ ARGV.push 'partial' unless ranges.empty?
613
+
614
+ # optionally save a snapshot
615
+ if ARGV.include? 'restore'
616
+ log :snap, 'restore'
617
+ Dir.chdir $BASE
618
+ FileUtils.rm_rf "work"
619
+ FileUtils.cp_r "snapshot", "work", :preserve => true
620
+ Dir.chdir $WORK
621
+ if $autorestart and File.directory? $autorestart
622
+ Dir.chdir $autorestart
623
+ restart_server
624
+ end
625
+ end
626
+
627
+ # run steps
628
+ e = nil
629
+ begin
630
+ $sections.each do |section, title, steps|
631
+ next if !ranges.empty? and
632
+ !ranges.any? do |range|
633
+ # was (in Ruby 1.8): range.include?(secsplit(section))
634
+ ss = secsplit(section)
635
+ (range.first <=> ss) <= 0 and (range.last <=> ss) >= 0
636
+ end
637
+ head section, title
638
+ steps.call
639
+ end
640
+ rescue Exception => e
641
+ $x.pre :class => 'traceback' do
642
+ STDERR.puts e.inspect
643
+ $x.text! "#{e.inspect}\n"
644
+ e.backtrace.each {|line| $x.text! " #{line}\n"}
645
+ end
646
+ ensure
647
+ if e.class != SystemExit
648
+ $cleanup.call if $cleanup
649
+
650
+ # terminate server
651
+ Process.kill "INT", $server if $server
652
+ Process.wait($server) if $server
653
+
654
+ # optionally save a snapshot
655
+ if ARGV.include? 'save'
656
+ log :snap, 'save'
657
+ Dir.chdir $BASE
658
+ FileUtils.rm_rf "snapshot"
659
+ FileUtils.cp_r "work", "snapshot", :preserve => true
660
+ end
661
+ end
662
+ end
663
+
664
+ $x.a(:class => 'toc', :id => 'env') {$x.h2 'Environment'}
665
+ cmd which_rails($rails) + ' -v'
666
+
667
+ cmd "#{$ruby} -v"
668
+ cmd 'gem -v'
669
+ cmd 'gem list'
670
+ cmd 'echo $RUBYLIB | sed "s/:/\n/g"'
671
+
672
+ $x.a(:class => 'toc', :id => 'todos') {$x.h2 'Todos'}
673
+ $x.ul :class => 'todos'
674
+ end
675
+ end
676
+
677
+ # output results as HTML, after inserting style and toc information
678
+ $x.target![/<style.*?>()/,1] = "\n#{$style.target!.strip.gsub(/^/,' '*6)}\n"
679
+ $x.target!.sub! /<ul class="toc"\/>/,
680
+ "<ul class=\"toc\">\n#{$toc.target!.gsub(/^/,' '*6)} </ul>"
681
+ $x.target!.sub! /<ul class="todos"\/>/,
682
+ "<ul class=\"todos\">\n#{$todos.target!.gsub(/^/,' '*6)} </ul>"
683
+ $x.target!.gsub! '<strong/>', '<strong></strong>'
684
+ log :WRITE, "#{$output}.html"
685
+ open("#{$BASE}/#{$output}.html",'w') do |file|
686
+ file.write <<-EOF.unindent(6)
687
+ <!DOCTYPE html
688
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
689
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
690
+ EOF
691
+ file.write $x.target!
692
+ end
693
+
694
+ # run tests
695
+ if $checker
696
+ log :CHECK, "#{$output}.html"
697
+ Dir.chdir $BASE
698
+ STDOUT.puts
699
+ if $checker =~ /^[-\w]+$/
700
+ require File.join($BASE,$checker)
701
+ else
702
+ require $checker
703
+ end
704
+ end
705
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Gorp
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 7
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end unless defined?(Gorp::VERSION)
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gorp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.1
5
+ platform: ruby
6
+ authors:
7
+ - Sam Ruby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-25 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: arel
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: builder
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: erubis
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: rack
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-test
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: rake
67
+ type: :runtime
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ - !ruby/object:Gem::Dependency
76
+ name: sqlite3-ruby
77
+ type: :runtime
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ - !ruby/object:Gem::Dependency
86
+ name: tzinfo
87
+ type: :runtime
88
+ version_requirement:
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ description: " Enables the creation of scenarios that involve creating a rails project,\n starting and stoppping of servers, generating projects, editing files,\n issuing http requests, running of commands, etc. Output is captured as\n a single HTML file that can be viewed locally or uploaded.\n\n Additionally, there is support for verification, in the form of defining\n assertions based on selections (typically CSS) against the generated HTML.\n"
96
+ email: rubys@intertwingly.net
97
+ executables: []
98
+
99
+ extensions: []
100
+
101
+ extra_rdoc_files:
102
+ - README
103
+ - lib/version.rb
104
+ - lib/gorp.rb
105
+ - lib/gorp/test.rb
106
+ files:
107
+ - README
108
+ - Rakefile
109
+ - Manifest
110
+ - gorp.gemspec
111
+ - lib/version.rb
112
+ - lib/gorp.rb
113
+ - lib/gorp/test.rb
114
+ has_rdoc: true
115
+ homepage: http://github.com/rubys/gorp
116
+ licenses: []
117
+
118
+ post_install_message:
119
+ rdoc_options:
120
+ - --line-numbers
121
+ - --inline-source
122
+ - --title
123
+ - Gorp
124
+ - --main
125
+ - README
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: "0"
133
+ version:
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: "1.2"
139
+ version:
140
+ requirements: []
141
+
142
+ rubyforge_project: gorp
143
+ rubygems_version: 1.3.3
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: Rails scenario testing support library
147
+ test_files: []
148
+