testbot 0.5.6 → 0.5.7
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/CHANGELOG +5 -2
- data/README.markdown +11 -4
- data/Rakefile +1 -1
- data/lib/generators/testbot/templates/testbot.yml.erb +2 -0
- data/lib/requester/requester.rb +6 -10
- data/lib/server/job.rb +1 -2
- data/lib/server/status/status.html +0 -1
- data/lib/shared/adapters/adapter.rb +1 -1
- data/lib/shared/adapters/cucumber_adapter.rb +47 -0
- data/lib/shared/adapters/rspec_adapter.rb +30 -0
- data/lib/shared/color.rb +16 -0
- data/lib/shared/version.rb +1 -1
- data/lib/tasks/testbot.rake +0 -2
- data/test/{test_integration.rb → integration_test.rb} +0 -0
- data/test/requester/{test_requester.rb → requester_test.rb} +81 -57
- data/test/requester/testbot.yml +7 -0
- data/test/requester/testbot_with_erb.yml +2 -0
- data/test/runner/{test_job.rb → job_test.rb} +0 -0
- data/test/server/{test_group.rb → group_test.rb} +0 -0
- data/test/server/{test_server.rb → server_test.rb} +0 -0
- data/test/shared/adapters/{test_adapter.rb → adapter_test.rb} +0 -0
- data/test/shared/adapters/cucumber_adapter_test.rb +72 -0
- data/test/shared/adapters/helpers/{test_ruby_env.rb → ruby_env_test.rb} +0 -0
- data/test/shared/adapters/rspec_adapter_test.rb +109 -0
- data/test/shared/{test_testbot.rb → testbot_test.rb} +0 -0
- data/testbot.gemspec +2 -0
- metadata +46 -14
- data/lib/server/status/javascripts/sammy-0.6.2.min.js +0 -5
data/CHANGELOG
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
0.5.7
|
2
|
+
|
3
|
+
Added @meeiw's patch to support ERB in config. Added test result summarization for RSpec and Cucumber.
|
4
|
+
|
1
5
|
0.5.6
|
2
6
|
|
3
7
|
Removed CPU usage check before running jobs (issue #25).
|
@@ -32,8 +36,7 @@ Removed all remaining native dependencies to make testbot simpler to install.
|
|
32
36
|
|
33
37
|
0.4.7
|
34
38
|
|
35
|
-
|
36
|
-
- No longer dependent on mongel, now using webrat.
|
39
|
+
Refactored the code into modules with one directory for each. No longer dependent on mongel, now using webrick.
|
37
40
|
|
38
41
|
0.4.6
|
39
42
|
|
data/README.markdown
CHANGED
@@ -2,6 +2,8 @@ Testbot is a test distribution tool that works with Rails, RSpec, RSpec2, Test::
|
|
2
2
|
|
3
3
|
Using testbot on 11 machines (25 cores) we got our test suite down to **2 minutes from 30**. [More examples of how testbot is used](http://github.com/joakimk/testbot/wiki/How-testbot-is-used).
|
4
4
|
|
5
|
+
If you intend to use testbot with cloud computing (like EC2), take a look at [TestbotCloud](https://github.com/joakimk/testbot_cloud).
|
6
|
+
|
5
7
|
Installing
|
6
8
|
----
|
7
9
|
|
@@ -60,7 +62,9 @@ Running tests:
|
|
60
62
|
|
61
63
|
Using testbot with Rails 2:
|
62
64
|
|
63
|
-
|
65
|
+
# Add testbot to your Gemfile if you use bundler. You also need the plugin because
|
66
|
+
# Rails 2 does not load raketasks from gems.
|
67
|
+
ruby script/plugin install git://github.com/joakimk/testbot.git -r 'refs/tags/v0.5.7'
|
64
68
|
script/generate testbot --connect 192.168.0.100
|
65
69
|
|
66
70
|
rake testbot:spec (or :rspec, :test, :features)
|
@@ -109,11 +113,14 @@ Contributing to testbot
|
|
109
113
|
----
|
110
114
|
|
111
115
|
First, get the tests to run:
|
116
|
+
|
112
117
|
bundle
|
113
118
|
rake
|
114
119
|
|
115
|
-
For development I recommend
|
116
|
-
|
120
|
+
For development I recommend using guard.
|
121
|
+
|
122
|
+
# OSX needs: gem install rb-fsevent
|
123
|
+
guard
|
117
124
|
|
118
125
|
Make your change (don't forget to write tests) and send me a pull request.
|
119
126
|
|
@@ -122,7 +129,7 @@ You can also contribute by adding to the [wiki](http://github.com/joakimk/testbo
|
|
122
129
|
How to add support for more test frameworks and/or programming languages
|
123
130
|
----
|
124
131
|
|
125
|
-
Add a **lib/adapters/framework_name_adapter.rb** file and update this readme.
|
132
|
+
Add a **lib/shared/adapters/framework_name_adapter.rb** file and update this readme.
|
126
133
|
|
127
134
|
More
|
128
135
|
----
|
data/Rakefile
CHANGED
data/lib/requester/requester.rb
CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'httparty'
|
3
3
|
require 'macaddr'
|
4
4
|
require 'ostruct'
|
5
|
+
require 'erb'
|
5
6
|
require File.dirname(__FILE__) + '/../shared/ssh_tunnel'
|
6
7
|
require File.expand_path(File.dirname(__FILE__) + '/../shared/testbot')
|
7
8
|
|
@@ -90,16 +91,15 @@ module Testbot::Requester
|
|
90
91
|
|
91
92
|
puts if config.simple_output
|
92
93
|
|
94
|
+
if adapter.respond_to?(:sum_results)
|
95
|
+
puts "\n" + adapter.sum_results(@build['results'])
|
96
|
+
end
|
97
|
+
|
93
98
|
@build["success"]
|
94
99
|
end
|
95
100
|
|
96
101
|
def self.create_by_config(path)
|
97
|
-
|
98
|
-
Requester.new(config)
|
99
|
-
end
|
100
|
-
|
101
|
-
def result_lines
|
102
|
-
@build['results'].split("\n").find_all { |line| line_is_result?(line) }.map { |line| line.chomp }
|
102
|
+
Requester.new(YAML.load(ERB.new(File.open(path).read).result))
|
103
103
|
end
|
104
104
|
|
105
105
|
private
|
@@ -120,10 +120,6 @@ module Testbot::Requester
|
|
120
120
|
[ '0.0.0.0', 'localhost', '127.0.0.1' ].include?(config.server_host)
|
121
121
|
end
|
122
122
|
|
123
|
-
def line_is_result?(line)
|
124
|
-
line =~ /\d+ fail/
|
125
|
-
end
|
126
|
-
|
127
123
|
def jruby?
|
128
124
|
RUBY_PLATFORM =~ /java/ || !!ENV['USE_JRUBY']
|
129
125
|
end
|
data/lib/server/job.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
module Testbot::Server
|
2
2
|
|
3
3
|
class Job < MemoryModel
|
4
|
-
|
5
|
-
#attribute :success, :boolean
|
6
4
|
|
7
5
|
def update(hash)
|
8
6
|
super(hash)
|
@@ -42,6 +40,7 @@ module Testbot::Server
|
|
42
40
|
Job.all.find_all { |job| job.taken_by == runner }.each { |job| job.update(:taken_at => nil) }
|
43
41
|
}
|
44
42
|
end
|
43
|
+
|
45
44
|
end
|
46
45
|
|
47
46
|
end
|
@@ -5,7 +5,6 @@
|
|
5
5
|
|
6
6
|
<link rel="stylesheet" href="/status/stylesheets/status.css" type="text/css" media="screen" charset="utf-8" />
|
7
7
|
<script type="text/javascript" charset="utf-8" src="/status/javascripts/jquery-1.4.4.min.js"></script>
|
8
|
-
<script type="text/javascript" charset="utf-8" src="/status/javascripts/sammy-0.6.2.min.js"></script>
|
9
8
|
<script type="text/javascript" charset="utf-8">
|
10
9
|
$(function () {
|
11
10
|
var status = new function() {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "/helpers/ruby_env"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "../color"))
|
2
3
|
|
3
4
|
class CucumberAdapter
|
4
5
|
|
@@ -36,8 +37,54 @@ class CucumberAdapter
|
|
36
37
|
pluralized
|
37
38
|
end
|
38
39
|
|
40
|
+
# This is an optional method. It gets passed the entire test result and summarizes it. See the tests.
|
41
|
+
def self.sum_results(text)
|
42
|
+
scenarios, steps = parse_scenarios_and_steps(text)
|
43
|
+
|
44
|
+
scenarios_line = "#{scenarios[:total]} scenarios (" + [
|
45
|
+
(Color.colorize("#{scenarios[:failed]} failed", :red) if scenarios[:failed] > 0),
|
46
|
+
(Color.colorize("#{scenarios[:undefined]} undefined", :orange) if scenarios[:undefined] > 0),
|
47
|
+
(Color.colorize("#{scenarios[:passed]} passed", :green) if scenarios[:passed] > 0)
|
48
|
+
].compact.join(', ') + ")"
|
49
|
+
|
50
|
+
steps_line = "#{steps[:total]} steps (" + [
|
51
|
+
(Color.colorize("#{steps[:failed]} failed", :red) if steps[:failed] > 0),
|
52
|
+
(Color.colorize("#{steps[:skipped]} skipped", :cyan) if steps[:skipped] > 0),
|
53
|
+
(Color.colorize("#{steps[:undefined]} undefined", :orange) if steps[:undefined] > 0),
|
54
|
+
(Color.colorize("#{steps[:passed]} passed", :green) if steps[:passed] > 0)
|
55
|
+
].compact.join(', ') + ")"
|
56
|
+
|
57
|
+
scenarios_line + "\n" + steps_line
|
58
|
+
end
|
59
|
+
|
39
60
|
private
|
40
61
|
|
62
|
+
def self.parse_scenarios_and_steps(text)
|
63
|
+
results = {
|
64
|
+
:scenarios => { :total => 0, :passed => 0, :failed => 0, :undefined => 0 },
|
65
|
+
:steps => { :total => 0, :passed => 0, :failed => 0, :skipped => 0, :undefined => 0 }
|
66
|
+
}
|
67
|
+
|
68
|
+
Color.strip(text).split("\n").each do |line|
|
69
|
+
type = line.include?("scenarios") ? :scenarios : :steps
|
70
|
+
|
71
|
+
if match = line.match(/\((.+)\)/)
|
72
|
+
results[type][:total] += line.split.first.to_i
|
73
|
+
parse_status_counts(results[type], match[1])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
[ results[:scenarios], results[:steps] ]
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.parse_status_counts(results, status_counts)
|
81
|
+
status_counts.split(', ').each do |part|
|
82
|
+
results.keys.each do |key|
|
83
|
+
results[key] += part.split.first.to_i if part.include?(key.to_s)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
41
88
|
def self.file_pattern
|
42
89
|
'**/**/*.feature'
|
43
90
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "/helpers/ruby_env"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "../color"))
|
2
3
|
|
3
4
|
class RspecAdapter
|
4
5
|
|
@@ -40,8 +41,37 @@ class RspecAdapter
|
|
40
41
|
'spec'
|
41
42
|
end
|
42
43
|
|
44
|
+
# This is an optional method. It gets passed the entire test result and summarizes it. See the tests.
|
45
|
+
def self.sum_results(results)
|
46
|
+
examples, failures, pending = 0, 0, 0
|
47
|
+
results.split("\n").each do |line|
|
48
|
+
line =~ /(\d+) examples?, (\d+) failures?(, (\d+) pending)?/
|
49
|
+
next unless $1
|
50
|
+
examples += $1.to_i
|
51
|
+
failures += $2.to_i
|
52
|
+
pending += $4.to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
result = [ pluralize(examples, 'example'), pluralize(failures, 'failure'), (pending > 0 ? "#{pending} pending" : nil) ].compact.join(', ')
|
56
|
+
if failures == 0 && pending == 0
|
57
|
+
Color.colorize(result, :green)
|
58
|
+
elsif failures == 0 && pending > 0
|
59
|
+
Color.colorize(result, :orange)
|
60
|
+
else
|
61
|
+
Color.colorize(result, :red)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
43
65
|
private
|
44
66
|
|
67
|
+
def self.pluralize(count, singular)
|
68
|
+
if count == 1
|
69
|
+
"#{count} #{singular}"
|
70
|
+
else
|
71
|
+
"#{count} #{singular}s"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
45
75
|
def self.file_pattern
|
46
76
|
'**/**/*_spec.rb'
|
47
77
|
end
|
data/lib/shared/color.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Color
|
2
|
+
def self.colorize(text, color)
|
3
|
+
colors = { :green => 32, :orange => 33, :red => 31, :cyan => 36 }
|
4
|
+
|
5
|
+
if colors[color]
|
6
|
+
"\033[#{colors[color]}m#{text}\033[0m"
|
7
|
+
else
|
8
|
+
raise "Color not implemented: #{color}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.strip(text)
|
13
|
+
text.gsub(/\e.+?m/, '')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
data/lib/shared/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Testbot
|
2
2
|
# Don't forget to update readme and changelog
|
3
3
|
def self.version
|
4
|
-
version = "0.5.
|
4
|
+
version = "0.5.7"
|
5
5
|
dev_version_file = File.join(File.dirname(__FILE__), '..', '..', 'DEV_VERSION')
|
6
6
|
if File.exists?(dev_version_file)
|
7
7
|
version += File.read(dev_version_file)
|
data/lib/tasks/testbot.rake
CHANGED
@@ -14,8 +14,6 @@ namespace :testbot do
|
|
14
14
|
path = custom_path ? "#{adapter.base_path}/#{custom_path}" : adapter.base_path
|
15
15
|
success = requester.run_tests(adapter, path)
|
16
16
|
|
17
|
-
puts
|
18
|
-
puts requester.result_lines.join("\n")
|
19
17
|
puts
|
20
18
|
puts "Finished in #{Time.now - start_time} seconds."
|
21
19
|
success
|
File without changes
|
@@ -39,21 +39,28 @@ module Testbot::Requester
|
|
39
39
|
flexmock(mock).should_receive(:size).and_return(0)
|
40
40
|
end
|
41
41
|
|
42
|
+
def fixture_path(local_path)
|
43
|
+
File.join(File.dirname(__FILE__), local_path)
|
44
|
+
end
|
45
|
+
|
42
46
|
context "self.create_by_config" do
|
43
47
|
|
44
48
|
should 'create a requester from config' do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
Requester.create_by_config("testbot.yml")
|
49
|
+
requester = Requester.create_by_config(fixture_path("testbot.yml"))
|
50
|
+
assert_equal 'hostname', requester.config.server_host
|
51
|
+
assert_equal '/path', requester.config.rsync_path
|
52
|
+
assert_equal '.git tmp', requester.config.rsync_ignores
|
53
|
+
assert_equal 'appname', requester.config.project
|
54
|
+
assert_equal false, requester.config.ssh_tunnel
|
55
|
+
assert_equal 'user', requester.config.server_user
|
56
|
+
assert_equal '50%', requester.config.available_runner_usage
|
54
57
|
end
|
55
58
|
|
56
|
-
|
59
|
+
should 'accept ERB-snippets in testbot.yml' do
|
60
|
+
requester = Requester.create_by_config(fixture_path("testbot_with_erb.yml"))
|
61
|
+
assert_equal 'dynamic_host', requester.config.server_host
|
62
|
+
assert_equal '50%', requester.config.available_runner_usage
|
63
|
+
end
|
57
64
|
end
|
58
65
|
|
59
66
|
context "initialize" do
|
@@ -93,15 +100,15 @@ module Testbot::Requester
|
|
93
100
|
:sizes => "10 20",
|
94
101
|
:jruby => false })
|
95
102
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
103
|
+
flexmock(HTTParty).should_receive(:get).and_return({ "done" => true, 'results' => '', "success" => true })
|
104
|
+
flexmock(requester).should_receive(:sleep)
|
105
|
+
flexmock(requester).should_receive(:puts)
|
106
|
+
flexmock(requester).should_receive(:system)
|
100
107
|
|
101
|
-
|
108
|
+
assert_equal true, requester.run_tests(RspecAdapter, 'spec')
|
102
109
|
end
|
103
110
|
|
104
|
-
should "
|
111
|
+
should "print the sum of results formatted by the adapter" do
|
105
112
|
requester = Requester.new(:server_host => "192.168.1.100")
|
106
113
|
|
107
114
|
flexmock(requester).should_receive(:find_tests).and_return([ 'spec/models/house_spec.rb', 'spec_models/car_spec.rb' ])
|
@@ -114,11 +121,34 @@ module Testbot::Requester
|
|
114
121
|
{ "done" => true, "results" => "job 2 done: ....job 1 done: ...." })
|
115
122
|
mock_file_sizes
|
116
123
|
|
117
|
-
|
118
|
-
|
119
|
-
|
124
|
+
flexmock(requester).should_receive(:sleep).times(2).with(1)
|
125
|
+
flexmock(requester).should_receive(:puts).once.with("job 2 done: ....")
|
126
|
+
flexmock(requester).should_receive(:puts).once.with("job 1 done: ....")
|
127
|
+
flexmock(requester).should_receive(:puts).once.with("\nformatted result")
|
120
128
|
|
121
|
-
|
129
|
+
flexmock(RspecAdapter).should_receive(:sum_results).with("job 2 done: ....job 1 done: ....").and_return("formatted result")
|
130
|
+
requester.run_tests(RspecAdapter, 'spec')
|
131
|
+
end
|
132
|
+
|
133
|
+
should "keep calling the server for results until done" do
|
134
|
+
requester = Requester.new(:server_host => "192.168.1.100")
|
135
|
+
|
136
|
+
flexmock(requester).should_receive(:find_tests).and_return([ 'spec/models/house_spec.rb', 'spec_models/car_spec.rb' ])
|
137
|
+
flexmock(requester).should_receive(:system)
|
138
|
+
|
139
|
+
flexmock(HTTParty).should_receive(:post).and_return('5')
|
140
|
+
|
141
|
+
flexmock(HTTParty).should_receive(:get).times(2).with("http://192.168.1.100:#{Testbot::SERVER_PORT}/builds/5",
|
142
|
+
:format => :json).and_return({ "done" => false, "results" => "job 2 done: ...." },
|
143
|
+
{ "done" => true, "results" => "job 2 done: ....job 1 done: ...." })
|
144
|
+
mock_file_sizes
|
145
|
+
|
146
|
+
flexmock(requester).should_receive(:sleep).times(2).with(1)
|
147
|
+
flexmock(requester).should_receive(:puts).once.with("job 2 done: ....")
|
148
|
+
flexmock(requester).should_receive(:puts).once.with("job 1 done: ....")
|
149
|
+
flexmock(requester).should_receive(:puts).once.with("\n\033[32m0 examples, 0 failures\033[0m")
|
150
|
+
|
151
|
+
requester.run_tests(RspecAdapter, 'spec')
|
122
152
|
end
|
123
153
|
|
124
154
|
should "return false if not successful" do
|
@@ -132,11 +162,12 @@ module Testbot::Requester
|
|
132
162
|
flexmock(HTTParty).should_receive(:get).once.with("http://192.168.1.100:#{Testbot::SERVER_PORT}/builds/5",
|
133
163
|
:format => :json).and_return({ "success" => false, "done" => true, "results" => "job 2 done: ....job 1 done: ...." })
|
134
164
|
|
135
|
-
|
136
|
-
|
137
|
-
|
165
|
+
flexmock(requester).should_receive(:sleep).once.with(1)
|
166
|
+
flexmock(requester).should_receive(:puts).once.with("job 2 done: ....job 1 done: ....")
|
167
|
+
flexmock(requester).should_receive(:puts).once.with("\n\033[32m0 examples, 0 failures\033[0m")
|
168
|
+
mock_file_sizes
|
138
169
|
|
139
|
-
|
170
|
+
assert_equal false, requester.run_tests(RspecAdapter, 'spec')
|
140
171
|
end
|
141
172
|
|
142
173
|
should "not print empty lines when there is no result" do
|
@@ -151,11 +182,12 @@ module Testbot::Requester
|
|
151
182
|
:format => :json).and_return({ "done" => false, "results" => "" },
|
152
183
|
{ "done" => true, "results" => "job 2 done: ....job 1 done: ...." })
|
153
184
|
|
154
|
-
|
155
|
-
|
156
|
-
|
185
|
+
flexmock(requester).should_receive(:sleep).times(2).with(1)
|
186
|
+
flexmock(requester).should_receive(:puts).once.with("job 2 done: ....job 1 done: ....")
|
187
|
+
flexmock(requester).should_receive(:puts).once.with("\n\033[32m0 examples, 0 failures\033[0m")
|
188
|
+
mock_file_sizes
|
157
189
|
|
158
|
-
|
190
|
+
requester.run_tests(RspecAdapter, 'spec')
|
159
191
|
end
|
160
192
|
|
161
193
|
should "sync the files to the server" do
|
@@ -166,13 +198,14 @@ module Testbot::Requester
|
|
166
198
|
|
167
199
|
flexmock(HTTParty).should_receive(:post).and_return('5')
|
168
200
|
flexmock(requester).should_receive(:sleep).once
|
201
|
+
flexmock(requester).should_receive(:puts)
|
169
202
|
flexmock(HTTParty).should_receive(:get).once.with("http://192.168.1.100:#{Testbot::SERVER_PORT}/builds/5",
|
170
203
|
:format => :json).and_return({ "done" => true, "results" => "" })
|
171
204
|
|
172
|
-
|
173
|
-
|
205
|
+
flexmock(requester).should_receive('system').with("rsync -az --delete -e ssh --exclude='.git' --exclude='tmp' . testbot@192.168.1.100:/path")
|
206
|
+
mock_file_sizes
|
174
207
|
|
175
|
-
|
208
|
+
requester.run_tests(RspecAdapter, 'spec')
|
176
209
|
end
|
177
210
|
|
178
211
|
should "just try again if the request encounters an error while running and print on the fith time" do
|
@@ -188,12 +221,13 @@ module Testbot::Requester
|
|
188
221
|
flexmock(HTTParty).should_receive(:get).times(1).with("http://192.168.1.100:#{Testbot::SERVER_PORT}/builds/5",
|
189
222
|
:format => :json).and_return({ "done" => true, "results" => "job 2 done: ....job 1 done: ...." })
|
190
223
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
224
|
+
flexmock(requester).should_receive(:sleep).times(6).with(1)
|
225
|
+
flexmock(requester).should_receive(:puts).once.with("Failed to get status: some connection error")
|
226
|
+
flexmock(requester).should_receive(:puts).once.with("job 2 done: ....job 1 done: ....")
|
227
|
+
flexmock(requester).should_receive(:puts).once.with("\n\033[32m0 examples, 0 failures\033[0m")
|
228
|
+
mock_file_sizes
|
195
229
|
|
196
|
-
|
230
|
+
requester.run_tests(RspecAdapter, 'spec')
|
197
231
|
end
|
198
232
|
|
199
233
|
should "just try again if the status returns as nil" do
|
@@ -208,11 +242,12 @@ module Testbot::Requester
|
|
208
242
|
:format => :json).and_return(nil,
|
209
243
|
{ "done" => true, "results" => "job 2 done: ....job 1 done: ...." })
|
210
244
|
|
211
|
-
|
212
|
-
|
213
|
-
|
245
|
+
flexmock(requester).should_receive(:sleep).times(2).with(1)
|
246
|
+
flexmock(requester).should_receive(:puts).once.with("job 2 done: ....job 1 done: ....")
|
247
|
+
flexmock(requester).should_receive(:puts).once.with("\n\033[32m0 examples, 0 failures\033[0m")
|
248
|
+
mock_file_sizes
|
214
249
|
|
215
|
-
|
250
|
+
requester.run_tests(RspecAdapter, 'spec')
|
216
251
|
end
|
217
252
|
|
218
253
|
should "remove unnessesary output from rspec when told to do so" do
|
@@ -227,14 +262,14 @@ module Testbot::Requester
|
|
227
262
|
:format => :json).and_return(nil,
|
228
263
|
{ "done" => true, "results" => "testbot4:\n....\n\nFinished in 84.333 seconds\n\n206 examples, 0 failures, 2 pending; testbot4:\n.F..\n\nFinished in 84.333 seconds\n\n206 examples, 0 failures, 2 pending" })
|
229
264
|
|
230
|
-
|
265
|
+
flexmock(requester).should_receive(:sleep).times(2).with(1)
|
231
266
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
267
|
+
# Imperfect match, includes "." in 84.333, but good enough.
|
268
|
+
flexmock(requester).should_receive(:print).once.with("......F...")
|
269
|
+
flexmock(requester).should_receive(:puts)
|
270
|
+
mock_file_sizes
|
236
271
|
|
237
|
-
|
272
|
+
requester.run_tests(RspecAdapter, 'spec')
|
238
273
|
end
|
239
274
|
|
240
275
|
should "use SSHTunnel when specified (with a port that does not collide with the runner)" do
|
@@ -330,17 +365,6 @@ module Testbot::Requester
|
|
330
365
|
|
331
366
|
end
|
332
367
|
|
333
|
-
context "result_lines" do
|
334
|
-
|
335
|
-
should "return all lines with results in them" do
|
336
|
-
results = "one\ntwo..\n... 0 failures\nthree"
|
337
|
-
requester = requester_with_result(results)
|
338
|
-
requester.run_tests(RspecAdapter, 'spec')
|
339
|
-
assert_equal [ '... 0 failures' ], requester.result_lines
|
340
|
-
end
|
341
|
-
|
342
|
-
end
|
343
|
-
|
344
368
|
end
|
345
369
|
|
346
370
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/shared/adapters/cucumber_adapter.rb'))
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
class CucumberAdapterTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "sum_results" do
|
8
|
+
|
9
|
+
should "be able to parse and sum results" do
|
10
|
+
results =<<STR
|
11
|
+
testbot4:/tmp/testbot
|
12
|
+
............................................................................................................................................................
|
13
|
+
|
14
|
+
13 scenarios (\033[32m13 passed\033[0m)
|
15
|
+
153 steps (\033[32m153 passed\033[0m)
|
16
|
+
0m25.537s
|
17
|
+
|
18
|
+
testbot3:/tmp/testbot
|
19
|
+
................................................................................................................
|
20
|
+
|
21
|
+
12 scenarios (\033[32m12 passed\033[0m)
|
22
|
+
109 steps (\033[32m109 passed\033[0m)
|
23
|
+
1m28.472s
|
24
|
+
STR
|
25
|
+
|
26
|
+
assert_equal "25 scenarios (25 passed)\n262 steps (262 passed)", Color.strip(CucumberAdapter.sum_results(results))
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
should "should handle undefined steps" do
|
31
|
+
results =<<STR
|
32
|
+
5 scenarios (1 failed, 1 undefined, 3 passed)
|
33
|
+
42 steps (1 failed, 3 skipped, 1 undefined, 37 passed)
|
34
|
+
|
35
|
+
5 scenarios (1 failed, 1 undefined, 3 passed)
|
36
|
+
42 steps (1 failed, 3 skipped, 1 undefined, 37 passed)
|
37
|
+
|
38
|
+
6 scenarios (6 passed)
|
39
|
+
80 steps (80 passed)
|
40
|
+
STR
|
41
|
+
|
42
|
+
assert_equal "16 scenarios (2 failed, 2 undefined, 12 passed)\n164 steps (2 failed, 6 skipped, 2 undefined, 154 passed)", Color.strip(CucumberAdapter.sum_results(results))
|
43
|
+
end
|
44
|
+
|
45
|
+
should "handle other combinations" do
|
46
|
+
results =<<STR
|
47
|
+
5 scenarios (1 failed, 1 undefined, 3 passed)
|
48
|
+
42 steps (1 failed, 1 undefined, 37 passed)
|
49
|
+
|
50
|
+
5 scenarios (1 failed, 1 undefined, 3 passed)
|
51
|
+
42 steps (3 skipped, 1 undefined, 37 passed)
|
52
|
+
|
53
|
+
6 scenarios (6 passed)
|
54
|
+
80 steps (80 passed)
|
55
|
+
STR
|
56
|
+
|
57
|
+
assert_equal "16 scenarios (2 failed, 2 undefined, 12 passed)\n164 steps (1 failed, 3 skipped, 2 undefined, 154 passed)", Color.strip(CucumberAdapter.sum_results(results))
|
58
|
+
end
|
59
|
+
|
60
|
+
should "colorize" do
|
61
|
+
results =<<STR
|
62
|
+
5 scenarios (1 failed, 1 undefined, 3 passed)
|
63
|
+
42 steps (1 failed, 3 skipped, 1 undefined, 37 passed)
|
64
|
+
STR
|
65
|
+
|
66
|
+
assert_equal "5 scenarios (\e[31m1 failed\e[0m, \e[33m1 undefined\e[0m, \e[32m3 passed\e[0m)\n42 steps (\e[31m1 failed\e[0m, \e[36m3 skipped\e[0m, \e[33m1 undefined\e[0m, \e[32m37 passed\e[0m)", CucumberAdapter.sum_results(results)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
end
|
File without changes
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/shared/adapters/rspec_adapter.rb'))
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
class RspecAdapterTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "sum_results" do
|
8
|
+
|
9
|
+
should "be able to parse and sum results" do
|
10
|
+
results =<<STR
|
11
|
+
srv-y5ei5:/tmp/testbot
|
12
|
+
..................FF..................................................
|
13
|
+
|
14
|
+
Finished in 4.962975 seconds
|
15
|
+
|
16
|
+
69 examples, 2 failures
|
17
|
+
|
18
|
+
|
19
|
+
testbot1:/tmp/testbot
|
20
|
+
.............F...........*........................
|
21
|
+
|
22
|
+
Finished in 9.987141 seconds
|
23
|
+
|
24
|
+
50 examples, 1 failure, 1 pending
|
25
|
+
|
26
|
+
testbot1:/tmp/testbot
|
27
|
+
.............FF.......****........................
|
28
|
+
|
29
|
+
Finished in 9.987141 seconds
|
30
|
+
|
31
|
+
50 examples, 2 failures, 3 pending
|
32
|
+
|
33
|
+
testbot1:/tmp/testbot
|
34
|
+
.
|
35
|
+
|
36
|
+
Finished in 9.987141 seconds
|
37
|
+
|
38
|
+
1 example, 0 failures, 0 pending
|
39
|
+
STR
|
40
|
+
assert_equal "170 examples, 5 failures, 4 pending", Color.strip(RspecAdapter.sum_results(results))
|
41
|
+
end
|
42
|
+
|
43
|
+
should "return 0 examples and failures for an empty resultset" do
|
44
|
+
assert_equal "0 examples, 0 failures", Color.strip(RspecAdapter.sum_results(""))
|
45
|
+
end
|
46
|
+
|
47
|
+
should "print in singular for examples" do
|
48
|
+
str =<<STR
|
49
|
+
testbot1:/tmp/testbot
|
50
|
+
.
|
51
|
+
|
52
|
+
Finished in 9.987141 seconds
|
53
|
+
|
54
|
+
1 example, 0 failures
|
55
|
+
STR
|
56
|
+
assert_equal "1 example, 0 failures", Color.strip(RspecAdapter.sum_results(str))
|
57
|
+
end
|
58
|
+
|
59
|
+
should "print in singular for failures" do
|
60
|
+
str =<<STR
|
61
|
+
testbot1:/tmp/testbot
|
62
|
+
F
|
63
|
+
|
64
|
+
Finished in 9.987141 seconds
|
65
|
+
|
66
|
+
0 example, 1 failures
|
67
|
+
STR
|
68
|
+
assert_equal "0 examples, 1 failure", Color.strip(RspecAdapter.sum_results(str))
|
69
|
+
end
|
70
|
+
|
71
|
+
should "make the result green if there is no failed or pending examples" do
|
72
|
+
str =<<STR
|
73
|
+
testbot1:/tmp/testbot
|
74
|
+
.
|
75
|
+
|
76
|
+
Finished in 9.987141 seconds
|
77
|
+
|
78
|
+
1 example, 0 failures
|
79
|
+
STR
|
80
|
+
assert_equal "\033[32m1 example, 0 failures\033[0m", RspecAdapter.sum_results(str)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "make the result orange if there is pending examples" do
|
84
|
+
str =<<STR
|
85
|
+
testbot1:/tmp/testbot
|
86
|
+
*
|
87
|
+
|
88
|
+
Finished in 9.987141 seconds
|
89
|
+
|
90
|
+
1 example, 0 failures, 1 pending
|
91
|
+
STR
|
92
|
+
assert_equal "\033[33m1 example, 0 failures, 1 pending\033[0m", RspecAdapter.sum_results(str)
|
93
|
+
end
|
94
|
+
|
95
|
+
should "make the results red if there is failed examples" do
|
96
|
+
str = <<STR
|
97
|
+
testbot1:/tmp/testbot
|
98
|
+
F
|
99
|
+
|
100
|
+
Finished in 9.987141 seconds
|
101
|
+
|
102
|
+
1 example, 1 failures
|
103
|
+
STR
|
104
|
+
assert_equal "\033[31m1 example, 1 failure\033[0m", RspecAdapter.sum_results(str)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
File without changes
|
data/testbot.gemspec
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: testbot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 5
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 5
|
9
|
-
-
|
10
|
-
version: 0.5.
|
9
|
+
- 7
|
10
|
+
version: 0.5.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- "Joakim Kolsj\xC3\xB6"
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-06-19 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -226,6 +226,34 @@ dependencies:
|
|
226
226
|
version: "0"
|
227
227
|
type: :development
|
228
228
|
version_requirements: *id014
|
229
|
+
- !ruby/object:Gem::Dependency
|
230
|
+
name: guard
|
231
|
+
prerelease: false
|
232
|
+
requirement: &id015 !ruby/object:Gem::Requirement
|
233
|
+
none: false
|
234
|
+
requirements:
|
235
|
+
- - ">="
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
hash: 3
|
238
|
+
segments:
|
239
|
+
- 0
|
240
|
+
version: "0"
|
241
|
+
type: :development
|
242
|
+
version_requirements: *id015
|
243
|
+
- !ruby/object:Gem::Dependency
|
244
|
+
name: guard-test
|
245
|
+
prerelease: false
|
246
|
+
requirement: &id016 !ruby/object:Gem::Requirement
|
247
|
+
none: false
|
248
|
+
requirements:
|
249
|
+
- - ">="
|
250
|
+
- !ruby/object:Gem::Version
|
251
|
+
hash: 3
|
252
|
+
segments:
|
253
|
+
- 0
|
254
|
+
version: "0"
|
255
|
+
type: :development
|
256
|
+
version_requirements: *id016
|
229
257
|
description: Testbot is a test distribution tool that works with Rails, RSpec, RSpec2, Test::Unit and Cucumber.
|
230
258
|
email:
|
231
259
|
- joakim.kolsjo@gmail.com
|
@@ -250,7 +278,6 @@ files:
|
|
250
278
|
- lib/server/runner.rb
|
251
279
|
- lib/server/server.rb
|
252
280
|
- lib/server/status/javascripts/jquery-1.4.4.min.js
|
253
|
-
- lib/server/status/javascripts/sammy-0.6.2.min.js
|
254
281
|
- lib/server/status/status.html
|
255
282
|
- lib/server/status/stylesheets/status.css
|
256
283
|
- lib/shared/adapters/adapter.rb
|
@@ -259,6 +286,7 @@ files:
|
|
259
286
|
- lib/shared/adapters/rspec2_adapter.rb
|
260
287
|
- lib/shared/adapters/rspec_adapter.rb
|
261
288
|
- lib/shared/adapters/test_unit_adapter.rb
|
289
|
+
- lib/shared/color.rb
|
262
290
|
- lib/shared/simple_daemonize.rb
|
263
291
|
- lib/shared/ssh_tunnel.rb
|
264
292
|
- lib/shared/testbot.rb
|
@@ -273,14 +301,18 @@ files:
|
|
273
301
|
- test/fixtures/local/spec/models/house_spec.rb
|
274
302
|
- test/fixtures/local/spec/spec.opts
|
275
303
|
- test/fixtures/local/tmp/restart.txt
|
276
|
-
- test/
|
277
|
-
- test/
|
278
|
-
- test/
|
279
|
-
- test/
|
280
|
-
- test/
|
281
|
-
- test/
|
282
|
-
- test/
|
283
|
-
- test/
|
304
|
+
- test/integration_test.rb
|
305
|
+
- test/requester/requester_test.rb
|
306
|
+
- test/requester/testbot.yml
|
307
|
+
- test/requester/testbot_with_erb.yml
|
308
|
+
- test/runner/job_test.rb
|
309
|
+
- test/server/group_test.rb
|
310
|
+
- test/server/server_test.rb
|
311
|
+
- test/shared/adapters/adapter_test.rb
|
312
|
+
- test/shared/adapters/cucumber_adapter_test.rb
|
313
|
+
- test/shared/adapters/helpers/ruby_env_test.rb
|
314
|
+
- test/shared/adapters/rspec_adapter_test.rb
|
315
|
+
- test/shared/testbot_test.rb
|
284
316
|
- Gemfile
|
285
317
|
- .gemtest
|
286
318
|
- Rakefile
|
@@ -318,7 +350,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
318
350
|
requirements: []
|
319
351
|
|
320
352
|
rubyforge_project:
|
321
|
-
rubygems_version: 1.
|
353
|
+
rubygems_version: 1.6.2
|
322
354
|
signing_key:
|
323
355
|
specification_version: 3
|
324
356
|
summary: A test distribution tool.
|
@@ -1,5 +0,0 @@
|
|
1
|
-
// -- Sammy -- /sammy.js
|
2
|
-
// http://code.quirkey.com/sammy
|
3
|
-
// Version: 0.6.2
|
4
|
-
// Built: Mon Oct 11 12:41:51 -0700 2010
|
5
|
-
(function(g,i){var n,f="([^/]+)",j=/:([\w\d]+)/g,k=/\?([^#]*)$/,b=function(o){return Array.prototype.slice.call(o)},c=function(o){return Object.prototype.toString.call(o)==="[object Function]"},l=function(o){return Object.prototype.toString.call(o)==="[object Array]"},h=decodeURIComponent,e=function(o){return o.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")},m=function(o){return function(p,q){return this.route.apply(this,[o,p,q])}},a={},d=[];n=function(){var p=b(arguments),q,o;n.apps=n.apps||{};if(p.length===0||p[0]&&c(p[0])){return n.apply(n,["body"].concat(p))}else{if(typeof(o=p.shift())=="string"){q=n.apps[o]||new n.Application();q.element_selector=o;if(p.length>0){g.each(p,function(r,s){q.use(s)})}if(q.element_selector!=o){delete n.apps[o]}n.apps[q.element_selector]=q;return q}}};n.VERSION="0.6.2";n.addLogger=function(o){d.push(o)};n.log=function(){var o=b(arguments);o.unshift("["+Date()+"]");g.each(d,function(q,p){p.apply(n,o)})};if(typeof i.console!="undefined"){if(c(console.log.apply)){n.addLogger(function(){i.console.log.apply(console,arguments)})}else{n.addLogger(function(){i.console.log(arguments)})}}else{if(typeof console!="undefined"){n.addLogger(function(){console.log.apply(console,arguments)})}}g.extend(n,{makeArray:b,isFunction:c,isArray:l});n.Object=function(o){return g.extend(this,o||{})};g.extend(n.Object.prototype,{escapeHTML:e,h:e,toHash:function(){var o={};g.each(this,function(q,p){if(!c(p)){o[q]=p}});return o},toHTML:function(){var o="";g.each(this,function(q,p){if(!c(p)){o+="<strong>"+q+"</strong> "+p+"<br />"}});return o},keys:function(o){var p=[];for(var q in this){if(!c(this[q])||!o){p.push(q)}}return p},has:function(o){return this[o]&&g.trim(this[o].toString())!=""},join:function(){var p=b(arguments);var o=p.shift();return p.join(o)},log:function(){n.log.apply(n,arguments)},toString:function(o){var p=[];g.each(this,function(r,q){if(!c(q)||o){p.push('"'+r+'": '+q.toString())}});return"Sammy.Object: {"+p.join(",")+"}"}});n.HashLocationProxy=function(p,o){this.app=p;this.is_native=false;this._startPolling(o)};n.HashLocationProxy.prototype={bind:function(){var o=this,p=this.app;g(i).bind("hashchange."+this.app.eventNamespace(),function(r,q){if(o.is_native===false&&!q){n.log("native hash change exists, using");o.is_native=true;i.clearInterval(n.HashLocationProxy._interval)}p.trigger("location-changed")});if(!n.HashLocationProxy._bindings){n.HashLocationProxy._bindings=0}n.HashLocationProxy._bindings++},unbind:function(){g(i).unbind("hashchange."+this.app.eventNamespace());n.HashLocationProxy._bindings--;if(n.HashLocationProxy._bindings<=0){i.clearInterval(n.HashLocationProxy._interval)}},getLocation:function(){var o=i.location.toString().match(/^[^#]*(#.+)$/);return o?o[1]:""},setLocation:function(o){return(i.location=o)},_startPolling:function(q){var p=this;if(!n.HashLocationProxy._interval){if(!q){q=10}var o=function(){var r=p.getLocation();if(!n.HashLocationProxy._last_location||r!=n.HashLocationProxy._last_location){i.setTimeout(function(){g(i).trigger("hashchange",[true])},13)}n.HashLocationProxy._last_location=r};o();n.HashLocationProxy._interval=i.setInterval(o,q)}}};n.Application=function(o){var p=this;this.routes={};this.listeners=new n.Object({});this.arounds=[];this.befores=[];this.namespace=(new Date()).getTime()+"-"+parseInt(Math.random()*1000,10);this.context_prototype=function(){n.EventContext.apply(this,arguments)};this.context_prototype.prototype=new n.EventContext();if(c(o)){o.apply(this,[this])}if(!this._location_proxy){this.setLocationProxy(new n.HashLocationProxy(this,this.run_interval_every))}if(this.debug){this.bindToAllEvents(function(r,q){p.log(p.toString(),r.cleaned_type,q||{})})}};n.Application.prototype=g.extend({},n.Object.prototype,{ROUTE_VERBS:["get","post","put","delete"],APP_EVENTS:["run","unload","lookup-route","run-route","route-found","event-context-before","event-context-after","changed","error","check-form-submission","redirect","location-changed"],_last_route:null,_location_proxy:null,_running:false,element_selector:"body",debug:false,raise_errors:false,run_interval_every:50,template_engine:null,toString:function(){return"Sammy.Application:"+this.element_selector},$element:function(){return g(this.element_selector)},use:function(){var o=b(arguments),q=o.shift(),p=q||"";try{o.unshift(this);if(typeof q=="string"){p="Sammy."+q;q=n[q]}q.apply(this,o)}catch(r){if(typeof q==="undefined"){this.error("Plugin Error: called use() but plugin ("+p.toString()+") is not defined",r)}else{if(!c(q)){this.error("Plugin Error: called use() but '"+p.toString()+"' is not a function",r)}else{this.error("Plugin Error",r)}}}return this},setLocationProxy:function(o){var p=this._location_proxy;this._location_proxy=o;if(this.isRunning()){if(p){p.unbind()}this._location_proxy.bind()}},route:function(s,p,u){var r=this,t=[],o,q;if(!u&&c(p)){p=s;u=p;s="any"}s=s.toLowerCase();if(p.constructor==String){j.lastIndex=0;while((q=j.exec(p))!==null){t.push(q[1])}p=new RegExp("^"+p.replace(j,f)+"$")}if(typeof u=="string"){u=r[u]}o=function(v){var w={verb:v,path:p,callback:u,param_names:t};r.routes[v]=r.routes[v]||[];r.routes[v].push(w)};if(s==="any"){g.each(this.ROUTE_VERBS,function(x,w){o(w)})}else{o(s)}return this},get:m("get"),post:m("post"),put:m("put"),del:m("delete"),any:m("any"),mapRoutes:function(p){var o=this;g.each(p,function(q,r){o.route.apply(o,r)});return this},eventNamespace:function(){return["sammy-app",this.namespace].join("-")},bind:function(o,q,s){var r=this;if(typeof s=="undefined"){s=q}var p=function(){var v,t,u;v=arguments[0];u=arguments[1];if(u&&u.context){t=u.context;delete u.context}else{t=new r.context_prototype(r,"bind",v.type,u,v.target)}v.cleaned_type=v.type.replace(r.eventNamespace(),"");s.apply(t,[v,u])};if(!this.listeners[o]){this.listeners[o]=[]}this.listeners[o].push(p);if(this.isRunning()){this._listen(o,p)}return this},trigger:function(o,p){this.$element().trigger([o,this.eventNamespace()].join("."),[p]);return this},refresh:function(){this.last_location=null;this.trigger("location-changed");return this},before:function(o,p){if(c(o)){p=o;o={}}this.befores.push([o,p]);return this},after:function(o){return this.bind("event-context-after",o)},around:function(o){this.arounds.push(o);return this},isRunning:function(){return this._running},helpers:function(o){g.extend(this.context_prototype.prototype,o);return this},helper:function(o,p){this.context_prototype.prototype[o]=p;return this},run:function(o){if(this.isRunning()){return false}var p=this;g.each(this.listeners.toHash(),function(q,r){g.each(r,function(t,s){p._listen(q,s)})});this.trigger("run",{start_url:o});this._running=true;this.last_location=null;if(this.getLocation()==""&&typeof o!="undefined"){this.setLocation(o)}this._checkLocation();this._location_proxy.bind();this.bind("location-changed",function(){p._checkLocation()});this.bind("submit",function(r){var q=p._checkFormSubmission(g(r.target).closest("form"));return(q===false)?r.preventDefault():false});g(i).bind("beforeunload",function(){p.unload()});return this.trigger("changed")},unload:function(){if(!this.isRunning()){return false}var o=this;this.trigger("unload");this._location_proxy.unbind();this.$element().unbind("submit").removeClass(o.eventNamespace());g.each(this.listeners.toHash(),function(p,q){g.each(q,function(s,r){o._unlisten(p,r)})});this._running=false;return this},bindToAllEvents:function(p){var o=this;g.each(this.APP_EVENTS,function(q,r){o.bind(r,p)});g.each(this.listeners.keys(true),function(r,q){if(o.APP_EVENTS.indexOf(q)==-1){o.bind(q,p)}});return this},routablePath:function(o){return o.replace(k,"")},lookupRoute:function(r,p){var q=this,o=false;this.trigger("lookup-route",{verb:r,path:p});if(typeof this.routes[r]!="undefined"){g.each(this.routes[r],function(t,s){if(q.routablePath(p).match(s.path)){o=s;return false}})}return o},runRoute:function(q,D,s,v){var r=this,B=this.lookupRoute(q,D),p,y,t,x,C,z,w,A,o;this.log("runRoute",[q,D].join(" "));this.trigger("run-route",{verb:q,path:D,params:s});if(typeof s=="undefined"){s={}}g.extend(s,this._parseQueryString(D));if(B){this.trigger("route-found",{route:B});if((A=B.path.exec(this.routablePath(D)))!==null){A.shift();g.each(A,function(E,F){if(B.param_names[E]){s[B.param_names[E]]=h(F)}else{if(!s.splat){s.splat=[]}s.splat.push(h(F))}})}p=new this.context_prototype(this,q,D,s,v);t=this.arounds.slice(0);C=this.befores.slice(0);w=[p].concat(s.splat);y=function(){var E;while(C.length>0){z=C.shift();if(r.contextMatchesOptions(p,z[0])){E=z[1].apply(p,[p]);if(E===false){return false}}}r.last_route=B;p.trigger("event-context-before",{context:p});E=B.callback.apply(p,w);p.trigger("event-context-after",{context:p});return E};g.each(t.reverse(),function(E,F){var G=y;y=function(){return F.apply(p,[G])}});try{o=y()}catch(u){this.error(["500 Error",q,D].join(" "),u)}return o}else{return this.notFound(q,D)}},contextMatchesOptions:function(r,t,p){var q=t;if(typeof q==="undefined"||q=={}){return true}if(typeof p==="undefined"){p=true}if(typeof q==="string"||c(q.test)){q={path:q}}if(q.only){return this.contextMatchesOptions(r,q.only,true)}else{if(q.except){return this.contextMatchesOptions(r,q.except,false)}}var o=true,s=true;if(q.path){if(c(q.path.test)){o=q.path.test(r.path)}else{o=(q.path.toString()===r.path)}}if(q.verb){s=q.verb===r.verb}return p?(s&&o):!(s&&o)},getLocation:function(){return this._location_proxy.getLocation()},setLocation:function(o){return this._location_proxy.setLocation(o)},swap:function(o){return this.$element().html(o)},templateCache:function(o,p){if(typeof p!="undefined"){return a[o]=p}else{return a[o]}},clearTemplateCache:function(){return a={}},notFound:function(q,p){var o=this.error(["404 Not Found",q,p].join(" "));return(q==="get")?o:true},error:function(p,o){if(!o){o=new Error()}o.message=[p,o.message].join(" ");this.trigger("error",{message:o.message,error:o});if(this.raise_errors){throw (o)}else{this.log(o.message,o)}},_checkLocation:function(){var o,p;o=this.getLocation();if(!this.last_location||this.last_location[0]!="get"||this.last_location[1]!=o){this.last_location=["get",o];p=this.runRoute("get",o)}return p},_getFormVerb:function(q){var p=g(q),r,o;o=p.find('input[name="_method"]');if(o.length>0){r=o.val()}if(!r){r=p[0].getAttribute("method")}return g.trim(r.toString().toLowerCase())},_checkFormSubmission:function(q){var o,r,t,s,p;this.trigger("check-form-submission",{form:q});o=g(q);r=o.attr("action");t=this._getFormVerb(o);if(!t||t==""){t="get"}this.log("_checkFormSubmission",o,r,t);if(t==="get"){this.setLocation(r+"?"+o.serialize());p=false}else{s=g.extend({},this._parseFormParams(o));p=this.runRoute(t,r,s,q.get(0))}return(typeof p=="undefined")?false:p},_parseFormParams:function(o){var r={},q=o.serializeArray(),p;for(p=0;p<q.length;p++){r=this._parseParamPair(r,q[p].name,q[p].value)}return r},_parseQueryString:function(r){var t={},q,p,s,o;q=r.match(k);if(q){p=q[1].split("&");for(o=0;o<p.length;o++){s=p[o].split("=");t=this._parseParamPair(t,h(s[0]),h(s[1]))}}return t},_parseParamPair:function(q,o,p){if(q[o]){if(l(q[o])){q[o].push(p)}else{q[o]=[q[o],p]}}else{q[o]=p}return q},_listen:function(o,p){return this.$element().bind([o,this.eventNamespace()].join("."),p)},_unlisten:function(o,p){return this.$element().unbind([o,this.eventNamespace()].join("."),p)}});n.RenderContext=function(o){this.event_context=o;this.callbacks=[];this.previous_content=null;this.content=null;this.next_engine=false;this.waiting=false};g.extend(n.RenderContext.prototype,{then:function(q){if(!c(q)){if(typeof q==="string"&&q in this.event_context){var p=this.event_context[q];q=function(r){return p.apply(this.event_context,[r])}}else{return this}}var o=this;if(this.waiting){this.callbacks.push(q)}else{this.wait();i.setTimeout(function(){var r=q.apply(o,[o.content,o.previous_content]);if(r!==false){o.next(r)}},13)}return this},wait:function(){this.waiting=true},next:function(o){this.waiting=false;if(typeof o!=="undefined"){this.previous_content=this.content;this.content=o}if(this.callbacks.length>0){this.then(this.callbacks.shift())}},load:function(o,p,r){var q=this;return this.then(function(){var s,t,v,u;if(c(p)){r=p;p={}}else{p=g.extend({},p)}if(r){this.then(r)}if(typeof o==="string"){v=(o.match(/\.json$/)||p.json);s=((v&&p.cache===true)||p.cache!==false);q.next_engine=q.event_context.engineFor(o);delete p.cache;delete p.json;if(p.engine){q.next_engine=p.engine;delete p.engine}if(s&&(t=this.event_context.app.templateCache(o))){return t}this.wait();g.ajax(g.extend({url:o,data:{},dataType:v?"json":null,type:"get",success:function(w){if(s){q.event_context.app.templateCache(o,w)}q.next(w)}},p));return false}else{if(o.nodeType){return o.innerHTML}if(o.selector){q.next_engine=o.attr("data-engine");if(p.clone===false){return o.remove()[0].innerHTML.toString()}else{return o[0].innerHTML.toString()}}}})},render:function(o,p,q){if(c(o)&&!p){return this.then(o)}else{if(!p&&this.content){p=this.content}return this.load(o).interpolate(p,o).then(q)}},partial:function(o,p){return this.render(o,p).swap()},send:function(){var q=this,p=b(arguments),o=p.shift();if(l(p[0])){p=p[0]}return this.then(function(r){p.push(function(s){q.next(s)});q.wait();o.apply(o,p);return false})},collect:function(s,r,o){var q=this;var p=function(){if(c(s)){r=s;s=this.content}var t=[],u=false;g.each(s,function(v,x){var w=r.apply(q,[v,x]);if(w.jquery&&w.length==1){w=w[0];u=true}t.push(w);return w});return u?t:t.join("")};return o?p():this.then(p)},renderEach:function(o,p,q,r){if(l(p)){r=q;q=p;p=null}return this.load(o).then(function(t){var s=this;if(!q){q=l(this.previous_content)?this.previous_content:[]}if(r){g.each(q,function(u,w){var x={},v=this.next_engine||o;p?(x[p]=w):(x=w);r(w,s.event_context.interpolate(t,x,v))})}else{return this.collect(q,function(u,w){var x={},v=this.next_engine||o;p?(x[p]=w):(x=w);return this.event_context.interpolate(t,x,v)},true)}})},interpolate:function(r,q,o){var p=this;return this.then(function(t,s){if(!r&&s){r=s}if(this.next_engine){q=this.next_engine;this.next_engine=false}var u=p.event_context.interpolate(t,r,q);return o?s+u:u})},swap:function(){return this.then(function(o){this.event_context.swap(o)}).trigger("changed",{})},appendTo:function(o){return this.then(function(p){g(o).append(p)}).trigger("changed",{})},prependTo:function(o){return this.then(function(p){g(o).prepend(p)}).trigger("changed",{})},replace:function(o){return this.then(function(p){g(o).html(p)}).trigger("changed",{})},trigger:function(o,p){return this.then(function(q){if(typeof p=="undefined"){p={content:q}}this.event_context.trigger(o,p)})}});n.EventContext=function(s,r,p,q,o){this.app=s;this.verb=r;this.path=p;this.params=new n.Object(q);this.target=o};n.EventContext.prototype=g.extend({},n.Object.prototype,{$element:function(){return this.app.$element()},engineFor:function(q){var p=this,o;if(c(q)){return q}q=q.toString();if((o=q.match(/\.([^\.]+)$/))){q=o[1]}if(q&&c(p[q])){return p[q]}if(p.app.template_engine){return this.engineFor(p.app.template_engine)}return function(r,s){return r}},interpolate:function(p,q,o){return this.engineFor(o).apply(this,[p,q])},render:function(o,p,q){return new n.RenderContext(this).render(o,p,q)},renderEach:function(o,p,q,r){return new n.RenderContext(this).renderEach(o,p,q,r)},load:function(o,p,q){return new n.RenderContext(this).load(o,p,q)},partial:function(o,p){return new n.RenderContext(this).partial(o,p)},send:function(){var o=new n.RenderContext(this);return o.send.apply(o,arguments)},redirect:function(){var q,p=b(arguments),o=this.app.getLocation();if(p.length>1){p.unshift("/");q=this.join.apply(this,p)}else{q=p[0]}this.trigger("redirect",{to:q});this.app.last_location=[this.verb,this.path];this.app.setLocation(q);if(o==q){this.app.trigger("location-changed")}},trigger:function(o,p){if(typeof p=="undefined"){p={}}if(!p.context){p.context=this}return this.app.trigger(o,p)},eventNamespace:function(){return this.app.eventNamespace()},swap:function(o){return this.app.swap(o)},notFound:function(){return this.app.notFound(this.verb,this.path)},json:function(o){return g.parseJSON(o)},toString:function(){return"Sammy.EventContext: "+[this.verb,this.path,this.params].join(" ")}});g.sammy=i.Sammy=n})(jQuery,window);
|