sauce 0.20.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +3 -10
- data/lib/sauce.rb +0 -2
- data/lib/sauce/client.rb +1 -8
- data/lib/sauce/job.rb +0 -5
- data/sauce.gemspec +3 -5
- metadata +5 -7
- data/lib/sauce/gateway_ext.rb +0 -40
- data/lib/sauce/tunnel.rb +0 -239
data/README.markdown
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
Sauce OnDemand is a Selenium testing cloud service, developed by Sauce
|
2
|
-
|
3
|
-
OnDemand.
|
1
|
+
Sauce OnDemand is a Selenium testing cloud service, developed by Sauce Labs Inc
|
2
|
+
(saucelabs.com). This is the Ruby client adapter for Sauce OnDemand.
|
4
3
|
|
5
4
|
Features
|
6
5
|
--------
|
7
6
|
|
8
7
|
* Drop-in replacement for Selenium::Client::Driver that takes care of connecting to Sauce OnDemand
|
9
8
|
* RSpec, Test::Unit, and Rails integration for tests, including automatic setup of Sauce Connect
|
10
|
-
* ActiveRecord-like interface for
|
9
|
+
* ActiveRecord-like interface for job metadata: Find/create/destroy
|
11
10
|
|
12
11
|
Install
|
13
12
|
-------
|
@@ -189,12 +188,6 @@ If you want tests to go a bit faster, globally install the gems with native exte
|
|
189
188
|
* gem install ffi sqlite3 json
|
190
189
|
* rvm use default
|
191
190
|
|
192
|
-
Plans
|
193
|
-
-----
|
194
|
-
|
195
|
-
* Webrat integration
|
196
|
-
* Extend to automatic retrieval of jobs logs, videos, reverse tunnels
|
197
|
-
|
198
191
|
Copyright
|
199
192
|
---------
|
200
193
|
|
data/lib/sauce.rb
CHANGED
data/lib/sauce/client.rb
CHANGED
@@ -9,7 +9,7 @@ module Sauce
|
|
9
9
|
|
10
10
|
attr_accessor :client
|
11
11
|
attr_accessor :protocol, :host, :port, :api_path, :api_version, :ip, :api_url
|
12
|
-
attr_accessor :
|
12
|
+
attr_accessor :jobs
|
13
13
|
|
14
14
|
def initialize(options={})
|
15
15
|
config = Sauce::Config.new
|
@@ -24,13 +24,6 @@ module Sauce
|
|
24
24
|
@api_url = "#{@protocol}://#{config.username}:#{config.access_key}@#{@host}:#{@port}/#{@api_path}/v#{@api_version}/#{config.username}/"
|
25
25
|
@client = RestClient::Resource.new @api_url
|
26
26
|
|
27
|
-
@tunnels = Sauce::Tunnel
|
28
|
-
@tunnels.client = @client
|
29
|
-
@tunnels.account = {
|
30
|
-
:username => config.username,
|
31
|
-
:access_key => config.access_key,
|
32
|
-
:ip => @ip}
|
33
|
-
|
34
27
|
@jobs = Sauce::Job
|
35
28
|
@jobs.client = @client
|
36
29
|
@jobs.account = {
|
data/lib/sauce/job.rb
CHANGED
data/sauce.gemspec
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{sauce}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "1.0.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Eric Allen", "Sean Grove", "Steven Hazel"]
|
9
|
-
s.date = %q{2011-
|
9
|
+
s.date = %q{2011-04-11}
|
10
10
|
s.default_executable = %q{sauce}
|
11
|
-
s.description = %q{A Ruby interface to Sauce
|
11
|
+
s.description = %q{A Ruby interface to Sauce OnDemand.}
|
12
12
|
s.email = %q{help@saucelabs.com}
|
13
13
|
s.executables = ["sauce"]
|
14
14
|
s.files = [
|
@@ -30,13 +30,11 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/sauce/client.rb",
|
31
31
|
"lib/sauce/config.rb",
|
32
32
|
"lib/sauce/connect.rb",
|
33
|
-
"lib/sauce/gateway_ext.rb",
|
34
33
|
"lib/sauce/heroku.rb",
|
35
34
|
"lib/sauce/integrations.rb",
|
36
35
|
"lib/sauce/job.rb",
|
37
36
|
"lib/sauce/raketasks.rb",
|
38
37
|
"lib/sauce/selenium.rb",
|
39
|
-
"lib/sauce/tunnel.rb",
|
40
38
|
"lib/sauce/utilities.rb",
|
41
39
|
"sauce.gemspec",
|
42
40
|
"support/sauce_connect",
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sauce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
- 20
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 1.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Eric Allen
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2011-
|
20
|
+
date: 2011-04-11 00:00:00 -07:00
|
21
21
|
default_executable: sauce
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|
@@ -142,7 +142,7 @@ dependencies:
|
|
142
142
|
version: 1.5.0
|
143
143
|
type: :runtime
|
144
144
|
version_requirements: *id008
|
145
|
-
description: A Ruby interface to Sauce
|
145
|
+
description: A Ruby interface to Sauce OnDemand.
|
146
146
|
email: help@saucelabs.com
|
147
147
|
executables:
|
148
148
|
- sauce
|
@@ -169,13 +169,11 @@ files:
|
|
169
169
|
- lib/sauce/client.rb
|
170
170
|
- lib/sauce/config.rb
|
171
171
|
- lib/sauce/connect.rb
|
172
|
-
- lib/sauce/gateway_ext.rb
|
173
172
|
- lib/sauce/heroku.rb
|
174
173
|
- lib/sauce/integrations.rb
|
175
174
|
- lib/sauce/job.rb
|
176
175
|
- lib/sauce/raketasks.rb
|
177
176
|
- lib/sauce/selenium.rb
|
178
|
-
- lib/sauce/tunnel.rb
|
179
177
|
- lib/sauce/utilities.rb
|
180
178
|
- sauce.gemspec
|
181
179
|
- support/sauce_connect
|
data/lib/sauce/gateway_ext.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# NOTE: This has been superseded by sauce/connect. Consider for deprecation
|
2
|
-
|
3
|
-
# http://groups.google.com/group/capistrano/browse_thread/thread/455c0c8a6faa9cc8?pli=1
|
4
|
-
class Net::SSH::Gateway
|
5
|
-
# Opens a SSH tunnel from a port on a remote host to a given host and port
|
6
|
-
# on the local side
|
7
|
-
# (equivalent to openssh -R parameter)
|
8
|
-
def open_remote(port, host, remote_port, remote_host = "127.0.0.1")
|
9
|
-
ensure_open!
|
10
|
-
|
11
|
-
begin
|
12
|
-
@session_mutex.synchronize do
|
13
|
-
result = @session.forward.remote(port, host, remote_port, remote_host)
|
14
|
-
end
|
15
|
-
|
16
|
-
if block_given?
|
17
|
-
begin
|
18
|
-
yield [remote_port, remote_host]
|
19
|
-
ensure
|
20
|
-
close_remote(remote_port, remote_host)
|
21
|
-
end
|
22
|
-
else
|
23
|
-
return [remote_port, remote_host]
|
24
|
-
end
|
25
|
-
rescue Errno::EADDRINUSE
|
26
|
-
retry
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Cancels port-forwarding over an open port that was previously opened via
|
31
|
-
# #open_remote.
|
32
|
-
def close_remote(port, host = "127.0.0.1")
|
33
|
-
puts "Close the remote port #{host}:#{port}!"
|
34
|
-
ensure_open!
|
35
|
-
|
36
|
-
@session_mutex.synchronize do
|
37
|
-
@session.forward.cancel_remote(port, host)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
data/lib/sauce/tunnel.rb
DELETED
@@ -1,239 +0,0 @@
|
|
1
|
-
# NOTE: This has been superseded by sauce/connect. Consider for deprecation
|
2
|
-
|
3
|
-
require 'net/telnet'
|
4
|
-
require 'net/ssh'
|
5
|
-
require 'net/ssh/gateway'
|
6
|
-
require 'sauce/gateway_ext'
|
7
|
-
|
8
|
-
module Sauce
|
9
|
-
# Interact with a Sauce Labs tunnel as if it were a ruby object
|
10
|
-
class Tunnel
|
11
|
-
class NoIDError < StandardError; end #nodoc
|
12
|
-
|
13
|
-
#{"Status": "running", "CreationTime": 1266545716, "ModificationTime": 1266545793, "Host": "ec2-184-73-16-13.compute-1.amazonaws.com", "LaunchTime": 1266545726, "Owner": "sgrove", "_id": "1090b4b0b50477cf40fc44c146b408a4", "Type": "tunnel", "id": "1090b4b0b50477cf40fc44c146b408a4", "DomainNames": ["sgrove.tst"]}
|
14
|
-
|
15
|
-
attr_accessor :owner, :access_key, :status
|
16
|
-
attr_accessor :id, :host, :domain_names, :timeout
|
17
|
-
attr_accessor :launch_time, :created_at, :updated_at
|
18
|
-
|
19
|
-
attr_accessor :application_address, :gateway, :port, :thread
|
20
|
-
|
21
|
-
# Get the class @@client.
|
22
|
-
# TODO: Consider metaprogramming this away
|
23
|
-
def self.client
|
24
|
-
@@client
|
25
|
-
end
|
26
|
-
|
27
|
-
# Set the class @@client.
|
28
|
-
# TODO: Consider metaprogramming this away
|
29
|
-
def self.client=(client)
|
30
|
-
@@client = client
|
31
|
-
end
|
32
|
-
|
33
|
-
# Get the class @@client.
|
34
|
-
# TODO: Consider metaprogramming this away
|
35
|
-
def self.account
|
36
|
-
@@account
|
37
|
-
end
|
38
|
-
|
39
|
-
# Set the class @@client.
|
40
|
-
# TODO: Consider metaprogramming this away
|
41
|
-
def self.account=(account)
|
42
|
-
@@account = account
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.first
|
46
|
-
self.all.first
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.last
|
50
|
-
self.all.last
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.all
|
54
|
-
responses = JSON.parse @@client[:tunnels].get.body
|
55
|
-
return responses.collect{|response| Sauce::Tunnel.new(response)}
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.destroy_all
|
59
|
-
self.all.each { |tunnel| tunnel.destroy }
|
60
|
-
end
|
61
|
-
|
62
|
-
# Creates a new tunnel machine
|
63
|
-
def self.create(options)
|
64
|
-
response = JSON.parse @@client[:tunnels].post(options.to_json, :content_type => 'application/json').body
|
65
|
-
#puts response.inspect
|
66
|
-
Tunnel.new response
|
67
|
-
end
|
68
|
-
|
69
|
-
# Creates an instance representing a machine, but does not actually create the machine. See #create for that.
|
70
|
-
def initialize(options)
|
71
|
-
build!(options)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Hits the destroy url for this tunnel, and then refreshes. Keep in mind it takes some time to completely teardown a tunnel.
|
75
|
-
def destroy
|
76
|
-
close_gateway
|
77
|
-
response = @@client["tunnels/#{@id}"].delete.body
|
78
|
-
refresh!
|
79
|
-
end
|
80
|
-
|
81
|
-
# Retrieves the latest information on this tunnel from the Sauce Labs' server
|
82
|
-
def refresh!
|
83
|
-
response = JSON.parse @@client["tunnels/#{@id}"].get.body
|
84
|
-
#puts "\Tunnel refresh with: #{response.inspect}"
|
85
|
-
build! response
|
86
|
-
self
|
87
|
-
end
|
88
|
-
|
89
|
-
# Shortcut method to find out if a tunnel is marked as dead
|
90
|
-
def preparing?
|
91
|
-
refresh!
|
92
|
-
status == "new" or status == "booting"
|
93
|
-
end
|
94
|
-
|
95
|
-
# Shortcut method to find out if a tunnel is marked as dead
|
96
|
-
def halting?
|
97
|
-
refresh!
|
98
|
-
status == "halting"
|
99
|
-
end
|
100
|
-
|
101
|
-
# Shortcut method to find out if a tunnel is marked as dead
|
102
|
-
def terminated?
|
103
|
-
refresh!
|
104
|
-
status == "terminated"
|
105
|
-
end
|
106
|
-
|
107
|
-
# A tunnel is healthy if 1.) its status is "running" and 2.) It says hello
|
108
|
-
def healthy?
|
109
|
-
# TODO: Implement 3.) We can reach through and touch a service on the reverse end of the tunnel
|
110
|
-
refresh!
|
111
|
-
=begin
|
112
|
-
puts "\tRunning? #{self.status == 'running'}"
|
113
|
-
puts "\tSays hello? #{self.says_hello?}"
|
114
|
-
puts "\tThread running? #{self.still_running?}"
|
115
|
-
=end
|
116
|
-
return true if not self.terminated? and self.status == "running" and self.says_hello? and self.still_running?
|
117
|
-
return false
|
118
|
-
end
|
119
|
-
|
120
|
-
# Sauce Labs' server will say hello on port 1025 as a sanity check. If no hello, something is wrong.
|
121
|
-
# TODO: Make it say hello on port 1025. Currently a hack.
|
122
|
-
def says_hello?(options={})
|
123
|
-
return false unless self.status == "running" and not @host.nil?
|
124
|
-
|
125
|
-
# TODO: Read from options if necessary
|
126
|
-
connection = {}
|
127
|
-
connection[:host] = @host
|
128
|
-
connection[:port] = 22
|
129
|
-
connection[:prompt] = /[$%#>] \z/n
|
130
|
-
connection[:telnet_mode] = true
|
131
|
-
connection[:timeout] = 10
|
132
|
-
|
133
|
-
host = Net::Telnet::new("Host" => connection[:host],
|
134
|
-
"Port" => connection[:port],
|
135
|
-
"Prompt" => connection[:prompt],
|
136
|
-
"Telnetmode" => connection[:telnet_mode],
|
137
|
-
"Timeout" => connection[:timeout])
|
138
|
-
line = host.lines.first.chomp
|
139
|
-
|
140
|
-
# Temporary workaround port 1025 problem
|
141
|
-
prefix = "SSH-2.0-Twisted"
|
142
|
-
line[0,prefix.length] == prefix
|
143
|
-
end
|
144
|
-
|
145
|
-
# Debug method
|
146
|
-
def mini_repair
|
147
|
-
refresh!
|
148
|
-
if not self.terminated? and self.status == "running" and self.says_hello?
|
149
|
-
if not self.still_running?
|
150
|
-
open_gateway
|
151
|
-
return true if self.still_running?
|
152
|
-
end
|
153
|
-
end
|
154
|
-
return false
|
155
|
-
end
|
156
|
-
|
157
|
-
# Opens a reverse ssh tunnel to the tunnel machine
|
158
|
-
def open_gateway
|
159
|
-
# TODO: Make ports configurable
|
160
|
-
|
161
|
-
@gateway = Net::SSH::Gateway.new(host, owner, {:password => @@account[:access_key]})
|
162
|
-
|
163
|
-
# Notes for anyone looking at this method
|
164
|
-
# gateway.open_remote(3000, # Port where your local application is running. Usually 3000 for rails dev
|
165
|
-
# "localhost", # Hostname/local ip your rails app is running on. Usually "localhost" for rails dev
|
166
|
-
# 80, # This is the port to run your selenium tests against, i.e. :url => "http://<some_host>:80"
|
167
|
-
# # <some_host> is the DomainNames you started the tunnel machine with.
|
168
|
-
# "0.0.0.0") # Do not change this unless your god has commanded you to do so. I'm serious.
|
169
|
-
port = 3000
|
170
|
-
local_host = "localhost"
|
171
|
-
remote_port = 80
|
172
|
-
#puts "This is the experimental non-blocking gateway_open. Well done."
|
173
|
-
#puts "gateway.open_remote(#{port}, #{local_host}, #{remote_port}, 0.0.0.0)"
|
174
|
-
#gateway.open_remote(pair[0], local_host, pair[1], "0.0.0.0") do |rp, rh|
|
175
|
-
|
176
|
-
@thread = Thread.new(host, port, remote_port, owner, @@account[:access_key]) do
|
177
|
-
Thread.pass
|
178
|
-
@gateway.open_remote(3000,
|
179
|
-
"localhost",
|
180
|
-
80,
|
181
|
-
"0.0.0.0") do |rp, rh|
|
182
|
-
while true do
|
183
|
-
sleep 10
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
@thread.run
|
189
|
-
end
|
190
|
-
|
191
|
-
# Closes the reverse ssh tunnel if open
|
192
|
-
def close_gateway
|
193
|
-
@thread.kill if @thread
|
194
|
-
end
|
195
|
-
|
196
|
-
# Returns true if this tunnel has a thread that needs to be monitored
|
197
|
-
def still_running?
|
198
|
-
#puts "\t\t#{@thread.inspect}.nil?"
|
199
|
-
not @thread.nil?
|
200
|
-
end
|
201
|
-
|
202
|
-
# Returns a json representation of the current state of the tunnel object
|
203
|
-
def to_json(options={})
|
204
|
-
json = {
|
205
|
-
:id => @id,
|
206
|
-
:owner => @owner,
|
207
|
-
:status => @status,
|
208
|
-
:host => @host,
|
209
|
-
:creation_time => @creation_time,
|
210
|
-
:start_time => @start_time,
|
211
|
-
:end_time => @end_time,
|
212
|
-
:domain_name => @domain_names
|
213
|
-
}
|
214
|
-
|
215
|
-
options[:except].each { |key| json.delete(key) } if options[:except]
|
216
|
-
json = json.select { |key,value| options[:only].include? key } if options[:only]
|
217
|
-
|
218
|
-
return json
|
219
|
-
end
|
220
|
-
|
221
|
-
protected
|
222
|
-
|
223
|
-
# Sets all internal variables from a hash
|
224
|
-
def build!(options)
|
225
|
-
options = options["tunnel"] unless options["tunnel"].nil?
|
226
|
-
#puts "\tBuild with #{options.inspect}"
|
227
|
-
@status = options["status"]
|
228
|
-
@owner = options["owner"]
|
229
|
-
@id = options["id"]
|
230
|
-
@host = options["host"]
|
231
|
-
@creation_time = options["creation_time"]
|
232
|
-
@start_time = options["start_time"]
|
233
|
-
@end_time = options["end_time"]
|
234
|
-
@domain_names = options["domain_names"]
|
235
|
-
|
236
|
-
raise NoIDError if @id.nil?
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|