sauce 0.20.0 → 1.0.0
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/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
|