spoofer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +93 -0
- data/Rakefile +25 -0
- data/features/checking_requests_were_made.feature +39 -0
- data/features/configuring_spoofer_via_http.feature +175 -0
- data/features/echoing_request_in_response.feature +76 -0
- data/features/logging_requests.feature +13 -0
- data/features/resetting_stubs.feature +16 -0
- data/features/steps/http_client_steps.rb +60 -0
- data/features/steps/logging_steps.rb +3 -0
- data/features/steps/shell_steps.rb +10 -0
- data/features/steps/spoofer_steps.rb +21 -0
- data/features/stubbing_requests_by_method.feature +25 -0
- data/features/stubbing_requests_by_path.feature +60 -0
- data/features/stubbing_requests_from_a_file.feature +33 -0
- data/features/stubbing_requests_with_parameters.feature +28 -0
- data/features/support/env.rb +15 -0
- data/features/support/hash_key_path.rb +7 -0
- data/features/support/http_client.rb +50 -0
- data/features/support/spoofer_runner.rb +7 -0
- data/features/using_rack_middlewares.feature +31 -0
- data/lib/spoofer.rb +104 -0
- data/lib/spoofer/api.rb +50 -0
- data/lib/spoofer/api/api_request.rb +49 -0
- data/lib/spoofer/api/stub.rb +39 -0
- data/lib/spoofer/fake_host.rb +102 -0
- data/lib/spoofer/fake_host/helpers.rb +11 -0
- data/lib/spoofer/fake_host/request_echo.rb +42 -0
- data/lib/spoofer/fake_host/stubbed_request.rb +73 -0
- data/lib/spoofer/version.rb +3 -0
- data/spec/fixtures/import_stubs.spoof +3 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/spoofer/fake_host_spec.rb +74 -0
- data/spec/spoofer/stubbed_request_spec.rb +16 -0
- data/spec/support/.keep +0 -0
- data/spec/support/matchers.rb +7 -0
- data/spec/support/pry.rb +1 -0
- data/spec/support/request_helper.rb +7 -0
- metadata +233 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: Resetting stubs
|
2
|
+
In order to create a deterministic clean slate at the beginning of my specs
|
3
|
+
As a developer
|
4
|
+
I want to be able to reset all previously configured request stubs
|
5
|
+
|
6
|
+
Scenario: Clearing a stubbed request
|
7
|
+
Given I have a spoofer specification with:
|
8
|
+
"""
|
9
|
+
Spoofer.mimic(:port => 11988).get("/some/path").returning("Hello World", 201)
|
10
|
+
"""
|
11
|
+
When I evaluate the code:
|
12
|
+
"""
|
13
|
+
Spoofer.reset_all!
|
14
|
+
"""
|
15
|
+
And I make an HTTP GET request to "http://localhost:11988/some/path"
|
16
|
+
Then I should receive an HTTP 404 response with an empty body
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Before do
|
2
|
+
@httpclient = HttpClient.new
|
3
|
+
end
|
4
|
+
|
5
|
+
def headers_from_string(string)
|
6
|
+
string.split("\n").inject({}) do |headers, header_string|
|
7
|
+
headers.tap do |h|
|
8
|
+
components = header_string.split(":")
|
9
|
+
h[components[0].strip] = components[1].strip
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
When /^I make an HTTP (POST|PUT) request to "([^\"]*)" with the payload:$/ do |http_method, url, payload|
|
15
|
+
@httpclient.perform_request_with_payload(url, http_method, payload)
|
16
|
+
end
|
17
|
+
|
18
|
+
When /^I make an HTTP (POST|PUT) request with a "([^\"]*)" content-type to "([^\"]*)" and the payload:$/ do |http_method, content_type, url, payload|
|
19
|
+
@httpclient.perform_request_with_payload(url, http_method, payload, :content_type => content_type)
|
20
|
+
end
|
21
|
+
|
22
|
+
When /^I make an HTTP (GET|POST|PUT|DELETE|HEAD) request to "([^\"]*)"$/ do |http_method, url|
|
23
|
+
@httpclient.perform_request(url, http_method)
|
24
|
+
end
|
25
|
+
|
26
|
+
When /^I make an HTTP (GET|POST|PUT|DELETE|HEAD) request to "([^\"]*)" with the header "([^\"]*)"$/ do |http_method, url, header|
|
27
|
+
@httpclient.perform_request(url, http_method, nil, headers_from_string(header))
|
28
|
+
end
|
29
|
+
|
30
|
+
Then /^I should receive an HTTP (\d+) response with an empty body$/ do |status_code|
|
31
|
+
steps %Q{
|
32
|
+
Then I should receive an HTTP #{status_code} response with a body matching ""
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
Then /^I should receive an HTTP (\d+) response with a body matching "([^\"]*)"$/ do |status_code, http_body|
|
37
|
+
@httpclient.should have_response_with_code_and_body(status_code.to_i, http_body)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^I should receive an HTTP (\d+) response with a body containing:$/ do |status_code, http_body|
|
41
|
+
@httpclient.should have_response_with_code_and_body(status_code.to_i, http_body)
|
42
|
+
end
|
43
|
+
|
44
|
+
Then /^I should receive an HTTP (\d+) response$/ do |status_code|
|
45
|
+
@httpclient.should have_response_with_code(status_code.to_i)
|
46
|
+
end
|
47
|
+
|
48
|
+
Then /^I should receive an HTTP (\d+) response with the value "([^\"]*)" for the header "([^\"]*)"$/ do |status_code, header_value, header_key|
|
49
|
+
@httpclient.should have_response_with_code_and_header(status_code.to_i, header_key, header_value)
|
50
|
+
end
|
51
|
+
|
52
|
+
Then /^I should receive an HTTP (\d+) response with the JSON value "([^\"]*)" for the key path "([^\"]*)"$/ do |status, json_value, key_path|
|
53
|
+
json = JSON.parse(@httpclient.last_response.to_s)
|
54
|
+
json.value_for_key_path(key_path).should == json_value
|
55
|
+
end
|
56
|
+
|
57
|
+
Then /^I should receive an HTTP (\d+) response with the Plist value "([^\"]*)" for the key path "([^\"]*)"$/ do |status, json_value, key_path|
|
58
|
+
plist = Plist.parse_xml(@httpclient.last_response.to_s)
|
59
|
+
plist.value_for_key_path(key_path).should == json_value
|
60
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
TEMP_FILES = []
|
2
|
+
|
3
|
+
Given /^the file "([^\"]*)" exists with the contents:$/ do |file_path, string|
|
4
|
+
File.open(file_path, "w") { |io| io.write(string) }
|
5
|
+
TEMP_FILES << file_path
|
6
|
+
end
|
7
|
+
|
8
|
+
After do
|
9
|
+
TEMP_FILES.each { |path| FileUtils.rm(path) if File.exist?(path) }
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Given /^I have a spoofer specification with:$/ do |string|
|
2
|
+
SpooferRunner.new.evaluate(string)
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^that Spoofer is running and accepting remote configuration on "([^\"]*)"$/ do |api_endpoint|
|
6
|
+
Spoofer.mimic(:port => 11988, :remote_configuration_path => api_endpoint)
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^that Spoofer is running and accepting remote configuration on "([^\"]*)" with the existing stubs:$/ do |api_endpoint, existing_stubs|
|
10
|
+
Spoofer.mimic(:port => 11988, :remote_configuration_path => api_endpoint) do
|
11
|
+
eval(existing_stubs)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I evaluate the code:$/ do |string|
|
16
|
+
eval(string)
|
17
|
+
end
|
18
|
+
|
19
|
+
After do
|
20
|
+
Spoofer.cleanup!
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Feature: Stubbing requests by path
|
2
|
+
In order to test a range of API endpoints and HTTP verbs
|
3
|
+
As a developer
|
4
|
+
I want to be able to stub requests to return specific responses depending on the request method
|
5
|
+
|
6
|
+
Scenario: Stubbing a POST request to return a 201 response
|
7
|
+
Given I have a spoofer specification with:
|
8
|
+
"""
|
9
|
+
Spoofer.mimic(:port => 11988).post("/some/path").returning("Hello World", 201)
|
10
|
+
"""
|
11
|
+
When I make an HTTP POST request to "http://localhost:11988/some/path"
|
12
|
+
Then I should receive an HTTP 201 response with a body matching "Hello World"
|
13
|
+
|
14
|
+
Scenario: Stubbing the same path with different responses for GET and POST
|
15
|
+
Given I have a spoofer specification with:
|
16
|
+
"""
|
17
|
+
Spoofer.mimic(:port => 11988) do
|
18
|
+
get("/some/path").returning("Some Record", 200)
|
19
|
+
post("/some/path").returning("Created", 201)
|
20
|
+
end
|
21
|
+
"""
|
22
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path"
|
23
|
+
Then I should receive an HTTP 200 response with a body matching "Some Record"
|
24
|
+
When I make an HTTP POST request to "http://localhost:11988/some/path"
|
25
|
+
Then I should receive an HTTP 201 response with a body matching "Created"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Feature: Stubbing requests by path
|
2
|
+
In order to test my app through its entire stack without depending on an external API
|
3
|
+
As a developer
|
4
|
+
I want to be able to stub requests to specific paths to return a canned response
|
5
|
+
|
6
|
+
Scenario: Stubbing a GET request to /some/path and return an empty response
|
7
|
+
Given I have a spoofer specification with:
|
8
|
+
"""
|
9
|
+
Spoofer.mimic(:port => 11988).get("/some/path")
|
10
|
+
"""
|
11
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path"
|
12
|
+
Then I should receive an HTTP 200 response with an empty body
|
13
|
+
|
14
|
+
Scenario: Stubbing a GET request to /some/path and returning a non-empty response
|
15
|
+
Given I have a spoofer specification with:
|
16
|
+
"""
|
17
|
+
Spoofer.mimic(:port => 11988).get("/some/path").returning("Hello World")
|
18
|
+
"""
|
19
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path"
|
20
|
+
Then I should receive an HTTP 200 response with a body matching "Hello World"
|
21
|
+
|
22
|
+
Scenario: Requesting an un-stubbed path and getting a 404 response
|
23
|
+
Given I have a spoofer specification with:
|
24
|
+
"""
|
25
|
+
Spoofer.mimic(:port => 11988).get("/some/path")
|
26
|
+
"""
|
27
|
+
When I make an HTTP GET request to "http://localhost:11988/some/other/path"
|
28
|
+
Then I should receive an HTTP 404 response with an empty body
|
29
|
+
|
30
|
+
Scenario: Stubbing a POST request to /some/path and return an empty response
|
31
|
+
Given I have a spoofer specification with:
|
32
|
+
"""
|
33
|
+
Spoofer.mimic(:port => 11988).post("/some/path")
|
34
|
+
"""
|
35
|
+
When I make an HTTP POST request to "http://localhost:11988/some/path"
|
36
|
+
Then I should receive an HTTP 200 response with an empty body
|
37
|
+
|
38
|
+
Scenario: Stubbing a PUT request to /some/path and return an empty response
|
39
|
+
Given I have a spoofer specification with:
|
40
|
+
"""
|
41
|
+
Spoofer.mimic(:port => 11988).put("/some/path")
|
42
|
+
"""
|
43
|
+
When I make an HTTP PUT request to "http://localhost:11988/some/path"
|
44
|
+
Then I should receive an HTTP 200 response with an empty body
|
45
|
+
|
46
|
+
Scenario: Stubbing a DELETE request to /some/path and return an empty response
|
47
|
+
Given I have a spoofer specification with:
|
48
|
+
"""
|
49
|
+
Spoofer.mimic(:port => 11988).delete("/some/path")
|
50
|
+
"""
|
51
|
+
When I make an HTTP DELETE request to "http://localhost:11988/some/path"
|
52
|
+
Then I should receive an HTTP 200 response with an empty body
|
53
|
+
|
54
|
+
Scenario: Stubbing a HEAD request to /some/path and return an empty response
|
55
|
+
Given I have a spoofer specification with:
|
56
|
+
"""
|
57
|
+
Spoofer.mimic(:port => 11988).head("/some/path")
|
58
|
+
"""
|
59
|
+
When I make an HTTP HEAD request to "http://localhost:11988/some/path"
|
60
|
+
Then I should receive an HTTP 200 response with an empty body
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Stubbing requests from a file
|
2
|
+
In order to pre-load Spoofer with a set of stubs
|
3
|
+
As a developer
|
4
|
+
I want to be able to store my stub configuration in a separate file and load it in at runtime
|
5
|
+
|
6
|
+
Scenario: Stubbing requests using a file
|
7
|
+
Given the file "/tmp/test.spoof" exists with the contents:
|
8
|
+
"""
|
9
|
+
get("/ping") { "pong" }
|
10
|
+
"""
|
11
|
+
And I have a spoofer specification with:
|
12
|
+
"""
|
13
|
+
Spoofer.mimic(:port => 11988) do
|
14
|
+
import "/tmp/test.mimic"
|
15
|
+
end
|
16
|
+
"""
|
17
|
+
When I make an HTTP GET request to "http://localhost:11988/ping"
|
18
|
+
Then I should receive an HTTP 200 response with a body matching "pong"
|
19
|
+
|
20
|
+
Scenario: Stubbed requests from a file persist even when Spoofer is cleared
|
21
|
+
Given the file "/tmp/test.spoof" exists with the contents:
|
22
|
+
"""
|
23
|
+
get("/ping") { "pong" }
|
24
|
+
"""
|
25
|
+
And I have a spoofer specification with:
|
26
|
+
"""
|
27
|
+
Spoofer.mimic(:port => 11988) do
|
28
|
+
import "/tmp/test.mimic"
|
29
|
+
end
|
30
|
+
Spoofer.reset_all!
|
31
|
+
"""
|
32
|
+
When I make an HTTP GET request to "http://localhost:11988/ping"
|
33
|
+
Then I should receive an HTTP 200 response with a body matching "pong"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Feature: Stubbing requests by path
|
2
|
+
In order to test requests that use specific query parameters
|
3
|
+
As a developer
|
4
|
+
I want to be able to only stub requests that have the correct parameters
|
5
|
+
|
6
|
+
Scenario: Accepting any parameters to a stubbed path
|
7
|
+
Given I have a spoofer specification with:
|
8
|
+
"""
|
9
|
+
Spoofer.mimic(:port => 11988).get("/some/path")
|
10
|
+
"""
|
11
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path?foo=bar"
|
12
|
+
Then I should receive an HTTP 200 response with an empty body
|
13
|
+
|
14
|
+
Scenario: Accepting specific parameters and matching
|
15
|
+
Given I have a spoofer specification with:
|
16
|
+
"""
|
17
|
+
Spoofer.mimic(:port => 11988).get("/some/path").with_query_parameters("foo" => "bar")
|
18
|
+
"""
|
19
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path?foo=bar"
|
20
|
+
Then I should receive an HTTP 200 response with an empty body
|
21
|
+
|
22
|
+
Scenario: Accepting specific parameters and matching
|
23
|
+
Given I have a spoofer specification with:
|
24
|
+
"""
|
25
|
+
Spoofer.mimic(:port => 11988).get("/some/path").with_query_parameters("foo" => "bar")
|
26
|
+
"""
|
27
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path?foo=baz"
|
28
|
+
Then I should receive an HTTP 404 response
|
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[.. .. lib]))
|
2
|
+
|
3
|
+
TEST_STDOUT = StringIO.new
|
4
|
+
|
5
|
+
Before do
|
6
|
+
if test_proxy = ENV["MIMIC_TEST_PROXY"]
|
7
|
+
HttpClient.use_proxy(test_proxy)
|
8
|
+
end
|
9
|
+
|
10
|
+
$stdout = TEST_STDOUT
|
11
|
+
end
|
12
|
+
|
13
|
+
After do
|
14
|
+
$stdout = STDOUT
|
15
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
class HttpClient
|
4
|
+
attr_reader :last_response
|
5
|
+
|
6
|
+
def self.use_proxy(proxy)
|
7
|
+
RestClient.proxy = proxy
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@last_response = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform_request(url, method, payload = nil, options={})
|
15
|
+
RestClient.send(method.downcase, url, options) do |response, request|
|
16
|
+
@last_response = response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform_request_with_payload(url, method, payload, options={})
|
21
|
+
RestClient.send(method.downcase, url, payload, options) do |response, request|
|
22
|
+
@last_response = response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_response_with_code_and_body?(status_code, response_body)
|
27
|
+
if @last_response
|
28
|
+
return @last_response.code.to_i == status_code && @last_response.to_s == response_body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_response_with_code?(status_code)
|
33
|
+
if @last_response
|
34
|
+
@last_response.code.to_i == status_code
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_response_with_code_and_header?(status_code, header_key, header_value)
|
39
|
+
if @last_response
|
40
|
+
@last_response.code.to_i == status_code &&
|
41
|
+
@last_response.headers[beautify_header(header_key)] == header_value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def beautify_header(header_key)
|
48
|
+
header_key.downcase.gsub(/-/, '_').to_sym
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: Injecting Rack middleware into the request chain for a stub
|
2
|
+
In order to test common scenarios (e.g. authentication)
|
3
|
+
As a developer
|
4
|
+
I want to be able to use Rack middlewares for certain responses
|
5
|
+
|
6
|
+
Scenario: Using Rack::Auth to simulate failed authentication
|
7
|
+
Given I have a spoofer specification with:
|
8
|
+
"""
|
9
|
+
Spoofer.mimic(:port => 11988) do
|
10
|
+
use Rack::Auth::Basic do |username, password|
|
11
|
+
end
|
12
|
+
|
13
|
+
get("/some/path")
|
14
|
+
end
|
15
|
+
"""
|
16
|
+
When I make an HTTP GET request to "http://localhost:11988/some/path"
|
17
|
+
Then I should receive an HTTP 401 response
|
18
|
+
|
19
|
+
Scenario: Using Rack::Auth to simulate successful authentication
|
20
|
+
Given I have a spoofer specification with:
|
21
|
+
"""
|
22
|
+
Spoofer.mimic(:port => 11988) do
|
23
|
+
use Rack::Auth::Basic do |username, password|
|
24
|
+
username == 'test' && password == 'pass'
|
25
|
+
end
|
26
|
+
|
27
|
+
get("/some/path")
|
28
|
+
end
|
29
|
+
"""
|
30
|
+
When I make an HTTP GET request to "http://test:pass@localhost:11988/some/path"
|
31
|
+
Then I should receive an HTTP 200 response
|
data/lib/spoofer.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'rack'
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
require 'plist'
|
6
|
+
require 'socket'
|
7
|
+
require 'digest'
|
8
|
+
require 'sinatra'
|
9
|
+
|
10
|
+
module Spoofer
|
11
|
+
autoload :API, 'spoofer/api'
|
12
|
+
autoload :FakeHost, 'spoofer/fake_host'
|
13
|
+
|
14
|
+
SPOOFER_DEFAULT_PORT = 11988
|
15
|
+
|
16
|
+
SPOOFER_DEFAULT_OPTIONS = {
|
17
|
+
hostname: 'localhost',
|
18
|
+
port: SPOOFER_DEFAULT_PORT,
|
19
|
+
remote_configuration_path: nil,
|
20
|
+
fork: true,
|
21
|
+
log: nil
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.poser(options = {}, &block)
|
25
|
+
options = SPOOFER_DEFAULT_OPTIONS.merge(options)
|
26
|
+
|
27
|
+
host = FakeHost.new(options).tap do |h|
|
28
|
+
h.instance_eval(&block) if block_given?
|
29
|
+
Server.instance.serve(h, options)
|
30
|
+
end
|
31
|
+
add_host(host)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.cleanup!
|
35
|
+
Spoofer::Server.instance.shutdown
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.reset_all!
|
39
|
+
@hosts.each { |h| h.clear }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.add_host(host)
|
45
|
+
host.tap { |h| (@hosts ||= []) << h }
|
46
|
+
end
|
47
|
+
|
48
|
+
class Server
|
49
|
+
include Singleton
|
50
|
+
|
51
|
+
def logger
|
52
|
+
@logger ||= Logger.new(StringIO.new)
|
53
|
+
end
|
54
|
+
|
55
|
+
def serve(app, options)
|
56
|
+
if options[:fork]
|
57
|
+
@thread = Thread.fork do
|
58
|
+
start_service(app, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
wait_for_service(app.hostname, options[:port])
|
62
|
+
|
63
|
+
else
|
64
|
+
start_service(app, options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def start_service(app, options)
|
69
|
+
Rack::Handler::Thin.run(app.url_map, {
|
70
|
+
:Port => options[:port],
|
71
|
+
:Logger => logger,
|
72
|
+
:AccessLog => logger,
|
73
|
+
})
|
74
|
+
end
|
75
|
+
|
76
|
+
def shutdown
|
77
|
+
Thread.kill(@thread) if @thread
|
78
|
+
end
|
79
|
+
|
80
|
+
# courtesy of http://is.gd/eoYho
|
81
|
+
|
82
|
+
def listening?(host, port)
|
83
|
+
begin
|
84
|
+
socket = TCPSocket.new(host, port)
|
85
|
+
socket.close unless socket.nil?
|
86
|
+
true
|
87
|
+
rescue Errno::ECONNREFUSED, SocketError,
|
88
|
+
Errno::EBADF, # Windows
|
89
|
+
Errno::EADDRNOTAVAIL # Windows
|
90
|
+
false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def wait_for_service(host, port, timeout = 5)
|
95
|
+
start_time = Time.now
|
96
|
+
|
97
|
+
until listening?(host, port)
|
98
|
+
if timeout && (Time.now > (start_time + timeout))
|
99
|
+
raise SocketError.new("Socket did not open within #{timeout} seconds")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|