bugsnag-maze-runner 6.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/bugsnag-print-load-paths +6 -0
- data/bin/download-logs +76 -0
- data/bin/maze-runner +136 -0
- data/bin/upload-app +56 -0
- data/lib/features/scripts/await-android-emulator.sh +11 -0
- data/lib/features/scripts/clear-android-app-data.sh +8 -0
- data/lib/features/scripts/force-stop-android-app.sh +8 -0
- data/lib/features/scripts/install-android-app.sh +15 -0
- data/lib/features/scripts/launch-android-app.sh +38 -0
- data/lib/features/scripts/launch-android-emulator.sh +15 -0
- data/lib/features/steps/android_steps.rb +51 -0
- data/lib/features/steps/app_automator_steps.rb +228 -0
- data/lib/features/steps/aws_sam_steps.rb +212 -0
- data/lib/features/steps/breadcrumb_steps.rb +50 -0
- data/lib/features/steps/browser_steps.rb +93 -0
- data/lib/features/steps/build_api_steps.rb +25 -0
- data/lib/features/steps/document_server_steps.rb +7 -0
- data/lib/features/steps/error_reporting_steps.rb +342 -0
- data/lib/features/steps/feature_flag_steps.rb +190 -0
- data/lib/features/steps/header_steps.rb +72 -0
- data/lib/features/steps/log_steps.rb +29 -0
- data/lib/features/steps/multipart_request_steps.rb +142 -0
- data/lib/features/steps/network_steps.rb +75 -0
- data/lib/features/steps/payload_steps.rb +234 -0
- data/lib/features/steps/proxy_steps.rb +34 -0
- data/lib/features/steps/query_parameter_steps.rb +31 -0
- data/lib/features/steps/request_assertion_steps.rb +107 -0
- data/lib/features/steps/runner_steps.rb +406 -0
- data/lib/features/steps/session_tracking_steps.rb +116 -0
- data/lib/features/steps/value_steps.rb +119 -0
- data/lib/features/support/env.rb +7 -0
- data/lib/features/support/internal_hooks.rb +260 -0
- data/lib/maze/appium_server.rb +112 -0
- data/lib/maze/assertions/request_set_assertions.rb +97 -0
- data/lib/maze/aws/sam.rb +112 -0
- data/lib/maze/bitbar_devices.rb +84 -0
- data/lib/maze/bitbar_utils.rb +112 -0
- data/lib/maze/browser_stack_devices.rb +160 -0
- data/lib/maze/browser_stack_utils.rb +164 -0
- data/lib/maze/browsers_bs.yml +220 -0
- data/lib/maze/browsers_cbt.yml +100 -0
- data/lib/maze/bugsnag_config.rb +42 -0
- data/lib/maze/capabilities.rb +126 -0
- data/lib/maze/checks/assert_check.rb +91 -0
- data/lib/maze/checks/noop_check.rb +34 -0
- data/lib/maze/compare.rb +161 -0
- data/lib/maze/configuration.rb +174 -0
- data/lib/maze/docker.rb +108 -0
- data/lib/maze/document_server.rb +46 -0
- data/lib/maze/driver/appium.rb +217 -0
- data/lib/maze/driver/browser.rb +138 -0
- data/lib/maze/driver/resilient_appium.rb +51 -0
- data/lib/maze/errors.rb +20 -0
- data/lib/maze/helper.rb +118 -0
- data/lib/maze/hooks/appium_hooks.rb +216 -0
- data/lib/maze/hooks/browser_hooks.rb +68 -0
- data/lib/maze/hooks/command_hooks.rb +9 -0
- data/lib/maze/hooks/hooks.rb +61 -0
- data/lib/maze/interactive_cli.rb +173 -0
- data/lib/maze/logger.rb +73 -0
- data/lib/maze/macos_utils.rb +14 -0
- data/lib/maze/network.rb +49 -0
- data/lib/maze/option/parser.rb +245 -0
- data/lib/maze/option/processor.rb +143 -0
- data/lib/maze/option/validator.rb +184 -0
- data/lib/maze/option.rb +64 -0
- data/lib/maze/plugins/bugsnag_reporting_plugin.rb +49 -0
- data/lib/maze/plugins/cucumber_report_plugin.rb +101 -0
- data/lib/maze/plugins/global_retry_plugin.rb +38 -0
- data/lib/maze/proxy.rb +114 -0
- data/lib/maze/request_list.rb +82 -0
- data/lib/maze/retry_handler.rb +76 -0
- data/lib/maze/runner.rb +149 -0
- data/lib/maze/sauce_labs_utils.rb +96 -0
- data/lib/maze/server.rb +207 -0
- data/lib/maze/servlets/base_servlet.rb +22 -0
- data/lib/maze/servlets/command_servlet.rb +44 -0
- data/lib/maze/servlets/log_servlet.rb +64 -0
- data/lib/maze/servlets/reflective_servlet.rb +69 -0
- data/lib/maze/servlets/servlet.rb +160 -0
- data/lib/maze/smart_bear_utils.rb +71 -0
- data/lib/maze/store.rb +15 -0
- data/lib/maze/terminating_server.rb +129 -0
- data/lib/maze/timers.rb +51 -0
- data/lib/maze/wait.rb +35 -0
- data/lib/maze.rb +27 -0
- metadata +371 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
module Servlets
|
5
|
+
# Receives HTTP requests and responds according to the parameters given, which are:
|
6
|
+
# - delay_ms - milliseconds to wait before responding
|
7
|
+
# - status - HTTP response code
|
8
|
+
# For GET requests these are expected to passed as GET parameters,
|
9
|
+
# for POST requests they are expected to be given as JSON fields.
|
10
|
+
class ReflectiveServlet < WEBrick::HTTPServlet::AbstractServlet
|
11
|
+
|
12
|
+
# Accepts a GET request to provide a reflective response to.
|
13
|
+
#
|
14
|
+
# @param request [HTTPRequest] The incoming GET request
|
15
|
+
# @param response [HTTPResponse] The response to return
|
16
|
+
def do_GET(request, response)
|
17
|
+
delay_ms = request.query['delay_ms']
|
18
|
+
status = request.query['status']
|
19
|
+
reflect response, delay_ms, status
|
20
|
+
end
|
21
|
+
|
22
|
+
# Accepts a POST request to provide a reflective response to.
|
23
|
+
#
|
24
|
+
# @param request [HTTPRequest] The incoming GET request
|
25
|
+
# @param response [HTTPResponse] The response to return
|
26
|
+
def do_POST(request, response)
|
27
|
+
|
28
|
+
content_type = request['Content-Type']
|
29
|
+
unless content_type == 'application/json'
|
30
|
+
msg = "Content-Type '#{content_type}' not supported - only application/json is supported at present"
|
31
|
+
$logger.error msg
|
32
|
+
response.status = 415
|
33
|
+
response.body = msg
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
body = JSON.parse(request.body)
|
38
|
+
delay_ms = body['delay_ms']
|
39
|
+
status = body['status']
|
40
|
+
|
41
|
+
reflect response, delay_ms, status
|
42
|
+
rescue JSON::ParserError => e
|
43
|
+
msg = "Unable to parse request as JSON: #{e.message}"
|
44
|
+
$logger.error msg
|
45
|
+
response.status = 418
|
46
|
+
rescue StandardError => e
|
47
|
+
$logger.error "Invalid request: #{e.message}"
|
48
|
+
response.status = 500
|
49
|
+
end
|
50
|
+
|
51
|
+
def reflect(response, delay_ms, status)
|
52
|
+
sleep delay_ms.to_i / 1000 unless delay_ms.nil?
|
53
|
+
response.status = status || 200
|
54
|
+
response.header['Access-Control-Allow-Origin'] = '*'
|
55
|
+
response.body = "Returned status #{status} after waiting #{delay_ms} ms"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Logs and returns a set of valid headers for this servlet.
|
59
|
+
#
|
60
|
+
# @param request [HTTPRequest] The incoming GET request
|
61
|
+
# @param response [HTTPResponse] The response to return
|
62
|
+
def do_OPTIONS(request, response)
|
63
|
+
super
|
64
|
+
|
65
|
+
response.header['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
module Servlets
|
5
|
+
|
6
|
+
# Receives and parses the requests and payloads sent from the test fixture
|
7
|
+
class Servlet < BaseServlet
|
8
|
+
# Constructor
|
9
|
+
#
|
10
|
+
# @param server [HTTPServer] WEBrick HTTPServer
|
11
|
+
# @param request_type [Symbol] Request type that the servlet will receive
|
12
|
+
def initialize(server, request_type)
|
13
|
+
super server
|
14
|
+
@request_type = request_type
|
15
|
+
@requests = Server.list_for request_type
|
16
|
+
end
|
17
|
+
|
18
|
+
# Logs an incoming GET WEBrick request.
|
19
|
+
#
|
20
|
+
# @param request [HTTPRequest] The incoming GET request
|
21
|
+
# @param _response [HTTPResponse] The response to return
|
22
|
+
def do_GET(request, _response)
|
23
|
+
log_request(request)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Logs and parses an incoming POST request.
|
27
|
+
# Parses `multipart/form-data` and `application/json` content-types.
|
28
|
+
# Parsed requests are added to the requests list.
|
29
|
+
#
|
30
|
+
# @param request [HTTPRequest] The incoming GET request
|
31
|
+
# @param response [HTTPResponse] The response to return
|
32
|
+
def do_POST(request, response)
|
33
|
+
log_request(request)
|
34
|
+
case request['Content-Type']
|
35
|
+
when %r{^multipart/form-data; boundary=([^;]+)}
|
36
|
+
boundary = WEBrick::HTTPUtils::dequote($1)
|
37
|
+
body = WEBrick::HTTPUtils.parse_form_data(request.body, boundary)
|
38
|
+
hash = {
|
39
|
+
body: body,
|
40
|
+
request: request
|
41
|
+
}
|
42
|
+
else
|
43
|
+
# "content-type" is assumed to be JSON (which mimics the behaviour of
|
44
|
+
# the actual API). This supports browsers that can't set this header for
|
45
|
+
# cross-domain requests (IE8/9)
|
46
|
+
digests = check_digest request
|
47
|
+
hash = {
|
48
|
+
body: JSON.parse(request.body),
|
49
|
+
request: request,
|
50
|
+
digests: digests
|
51
|
+
}
|
52
|
+
end
|
53
|
+
@requests.add(hash)
|
54
|
+
|
55
|
+
# For the response, delaying if configured to do so
|
56
|
+
response_delay_ms = Server.response_delay_ms
|
57
|
+
if response_delay_ms.positive?
|
58
|
+
$logger.info "Waiting #{response_delay_ms} milliseconds before responding"
|
59
|
+
sleep response_delay_ms / 1000.0
|
60
|
+
end
|
61
|
+
response.header['Access-Control-Allow-Origin'] = '*'
|
62
|
+
response.status = Server.status_code
|
63
|
+
rescue JSON::ParserError => e
|
64
|
+
msg = "Unable to parse request as JSON: #{e.message}"
|
65
|
+
if Maze.config.captured_invalid_requests.include? @request_type
|
66
|
+
$logger.error msg
|
67
|
+
Server.invalid_requests.add({
|
68
|
+
reason: msg,
|
69
|
+
request: request,
|
70
|
+
body: request.body
|
71
|
+
})
|
72
|
+
else
|
73
|
+
$logger.warn msg
|
74
|
+
end
|
75
|
+
rescue StandardError => e
|
76
|
+
if Maze.config.captured_invalid_requests.include? @request_type
|
77
|
+
$logger.error "Invalid request: #{e.message}"
|
78
|
+
Server.invalid_requests.add({
|
79
|
+
invalid: true,
|
80
|
+
reason: e.message,
|
81
|
+
request: {
|
82
|
+
request_uri: request.request_uri,
|
83
|
+
header: request.header,
|
84
|
+
body: request.inspect
|
85
|
+
}
|
86
|
+
})
|
87
|
+
else
|
88
|
+
$logger.warn "Invalid request: #{e.message}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Logs and returns a set of valid headers for this servlet.
|
93
|
+
#
|
94
|
+
# @param request [HTTPRequest] The incoming GET request
|
95
|
+
# @param response [HTTPResponse] The response to return
|
96
|
+
def do_OPTIONS(request, response)
|
97
|
+
super
|
98
|
+
|
99
|
+
response.header['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
|
100
|
+
response.status = Server.status_code
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def log_request(request)
|
106
|
+
$logger.debug "#{request.request_method} request received"
|
107
|
+
$logger.debug "URI: #{request.unparsed_uri}"
|
108
|
+
$logger.debug "HEADERS: #{request.raw_header}"
|
109
|
+
return if request.body.nil?
|
110
|
+
|
111
|
+
case request['Content-Type']
|
112
|
+
when nil
|
113
|
+
nil
|
114
|
+
when %r{^multipart/form-data; boundary=([^;]+)}
|
115
|
+
boundary = WEBrick::HTTPUtils.dequote(Regexp.last_match(1))
|
116
|
+
body = WEBrick::HTTPUtils.parse_form_data(request.body, boundary)
|
117
|
+
$logger.debug 'BODY:'
|
118
|
+
LogUtil.log_hash(Logger::Severity::DEBUG, body)
|
119
|
+
else
|
120
|
+
$logger.debug "BODY: #{JSON.pretty_generate(JSON.parse(request.body))}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Checks the Bugsnag-Integrity header, if present, against the request and based on configuration.
|
125
|
+
# If the header is present, if the digest must be correct. However, the header need only be present
|
126
|
+
# if configuration says so.
|
127
|
+
def check_digest(request)
|
128
|
+
header = request['Bugsnag-Integrity']
|
129
|
+
if header.nil? && Maze.config.enforce_bugsnag_integrity
|
130
|
+
raise 'Bugsnag-Integrity header must be present according to Maze.config.enforce_bugsnag_integrity'
|
131
|
+
end
|
132
|
+
return if header.nil?
|
133
|
+
|
134
|
+
# Header must have type and digest
|
135
|
+
parts = header.split ' '
|
136
|
+
raise "Invalid Bugsnag-Integrity header: #{header}" unless parts.size == 2
|
137
|
+
|
138
|
+
# Both digest types are stored whatever
|
139
|
+
sha1 = Digest::SHA1.hexdigest(request.body)
|
140
|
+
simple = request.body.bytesize
|
141
|
+
$logger.debug "DIGESTS computed: sha1=#{sha1} simple=#{simple}"
|
142
|
+
|
143
|
+
# Check digests match
|
144
|
+
case parts[0]
|
145
|
+
when 'sha1'
|
146
|
+
raise "Given sha1 #{parts[1]} does not match the computed #{sha1}" unless parts[1] == sha1
|
147
|
+
when 'simple'
|
148
|
+
raise "Given simple digest #{parts[1].inspect} does not match the computed #{simple.inspect}" unless parts[1].to_i == simple
|
149
|
+
else
|
150
|
+
raise "Invalid Bugsnag-Integrity digest type: #{parts[0]}"
|
151
|
+
end
|
152
|
+
|
153
|
+
{
|
154
|
+
sha1: sha1,
|
155
|
+
simple: simple
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
# Utils supporting the BrowserStack device farm integration
|
5
|
+
class SmartBearUtils
|
6
|
+
class << self
|
7
|
+
SB_READY_FILE = 'sb.ready'
|
8
|
+
SB_KILL_FILE = 'sb.kill'
|
9
|
+
|
10
|
+
# Starts the SmartBear local tunnel
|
11
|
+
#
|
12
|
+
# @param sb_local [String] path to the SBSecureTunnel binary
|
13
|
+
# @param username [String] Username to start the tunnel with
|
14
|
+
# @param access_key [String] CBT access key
|
15
|
+
# @param tunnel_name [String] Tunnel name
|
16
|
+
def start_local_tunnel(sb_local, username, access_key, tunnel_name=nil)
|
17
|
+
# Make sure the ready/kill files are already deleted
|
18
|
+
File.delete(SB_READY_FILE) if File.exist?(SB_READY_FILE)
|
19
|
+
File.delete(SB_KILL_FILE) if File.exist?(SB_KILL_FILE)
|
20
|
+
|
21
|
+
$logger.info 'Starting CBT SBSecureTunnel'
|
22
|
+
command = "#{sb_local} --username #{username} --authkey #{access_key} --acceptAllCerts " \
|
23
|
+
"--ready #{SB_READY_FILE} --kill #{SB_KILL_FILE}"
|
24
|
+
command << " --tunnelname #{tunnel_name}" unless tunnel_name.nil?
|
25
|
+
|
26
|
+
output = start_tunnel_thread(command)
|
27
|
+
|
28
|
+
success = Maze::Wait.new(timeout: 30).until do
|
29
|
+
File.exist?(SB_READY_FILE)
|
30
|
+
end
|
31
|
+
unless success
|
32
|
+
$logger.error "Failed: #{output}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Stops the local tunnel
|
37
|
+
def stop_local_tunnel
|
38
|
+
FileUtils.touch(SB_KILL_FILE)
|
39
|
+
Maze::Wait.new(timeout: 30).until do
|
40
|
+
!File.exist?(SB_READY_FILE)
|
41
|
+
end
|
42
|
+
File.delete(SB_READY_FILE) if File.exist?(SB_READY_FILE)
|
43
|
+
File.delete(SB_KILL_FILE) if File.exist?(SB_KILL_FILE)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def start_tunnel_thread(cmd)
|
49
|
+
executor = lambda do
|
50
|
+
Open3.popen2e(Maze::Runner.environment, cmd) do |_stdin, stdout_and_stderr, wait_thr|
|
51
|
+
|
52
|
+
output = []
|
53
|
+
stdout_and_stderr.each do |line|
|
54
|
+
output << line
|
55
|
+
$logger.debug('SBSecureTunnel') {line.chomp}
|
56
|
+
end
|
57
|
+
|
58
|
+
exit_status = wait_thr.value.to_i
|
59
|
+
$logger.debug "Exit status: #{exit_status}"
|
60
|
+
|
61
|
+
output.each { |line| $logger.warn('SBSecureTunnel') {line.chomp} } unless exit_status == 0
|
62
|
+
|
63
|
+
return [output, exit_status]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Thread.new(&executor)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/maze/store.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
# Provides a hash to store values for cross-request comparisons
|
5
|
+
class Store
|
6
|
+
class << self
|
7
|
+
# Returns the hash of stored values for the current scenario
|
8
|
+
#
|
9
|
+
# @return [Hash] The stored value hash
|
10
|
+
def values
|
11
|
+
@values ||= {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Maze
|
6
|
+
# Receives and terminates network connections without reading any data
|
7
|
+
class TerminatingServer
|
8
|
+
CONTINUE_RESPONSE = "HTTP/1.1 100 CONTINUE\n\r"
|
9
|
+
BAD_REQUEST_RESPONSE = "HTTP/1.1 400 BAD REQUEST\n\r"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Starts the socket accept loop in a separate thread
|
14
|
+
def start
|
15
|
+
# Only run a single server thread
|
16
|
+
return if running?
|
17
|
+
|
18
|
+
attempts = 0
|
19
|
+
loop do
|
20
|
+
|
21
|
+
@thread = Thread.new do
|
22
|
+
# Reset the received count
|
23
|
+
@received_requests = 0
|
24
|
+
|
25
|
+
Socket.tcp_server_loop(Maze.config.null_port) {|socket, _client_addrinfo|
|
26
|
+
$logger.info 'Terminating server received request'
|
27
|
+
@received_requests += 1
|
28
|
+
headers = receive_headers(socket)
|
29
|
+
|
30
|
+
body_length = headers['Content-Length']
|
31
|
+
receive_data(socket, body_length) unless body_length.nil?
|
32
|
+
|
33
|
+
end_connection(socket)
|
34
|
+
}
|
35
|
+
rescue StandardError => e
|
36
|
+
$logger.warn "Terminating server error: #{e.message}"
|
37
|
+
end
|
38
|
+
|
39
|
+
break if running?
|
40
|
+
|
41
|
+
# Bail out after 3 attempts
|
42
|
+
attempts += 1
|
43
|
+
raise 'Too many failed attempts to start terminating server' if attempts == 3
|
44
|
+
|
45
|
+
# Failed to start - sleep before retrying
|
46
|
+
$logger.info 'Retrying in 3 seconds'
|
47
|
+
sleep 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# The maximum string length to be received before disconnecting
|
52
|
+
#
|
53
|
+
# @return [Integer] The string length, defaults to 1MB
|
54
|
+
def max_received_size
|
55
|
+
@max_received_size ||= 1048576
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set the maximum string length to be received before disconnecting
|
59
|
+
#
|
60
|
+
# @param new_max_size [Integer] The new maximum size
|
61
|
+
def max_received_size=(new_max_size)
|
62
|
+
@max_received_size = new_max_size
|
63
|
+
end
|
64
|
+
|
65
|
+
# The response string sent to a connected client
|
66
|
+
#
|
67
|
+
# @return [String] The response string, defaults to "400/BAD REQUEST"
|
68
|
+
def response
|
69
|
+
@response ||= BAD_REQUEST_RESPONSE
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set the response string to an arbitrary value
|
73
|
+
#
|
74
|
+
# @param new_response [String] The new response
|
75
|
+
def response=(new_response)
|
76
|
+
@response = new_response
|
77
|
+
end
|
78
|
+
|
79
|
+
# Resets the response string to "400/BAD REQUEST" and the read size to 1MB
|
80
|
+
def reset_elements
|
81
|
+
@response = BAD_REQUEST_RESPONSE
|
82
|
+
@max_received_size = 1048576
|
83
|
+
end
|
84
|
+
|
85
|
+
# Whether the server thread is running
|
86
|
+
#
|
87
|
+
# @return [Boolean] If the server is running
|
88
|
+
def running?
|
89
|
+
@thread&.alive?
|
90
|
+
end
|
91
|
+
|
92
|
+
# Outputs the amount of times the server has received a connection on the last run
|
93
|
+
def received_request_count
|
94
|
+
@received_requests ||= 0
|
95
|
+
end
|
96
|
+
|
97
|
+
# Stops the socket accept loop if alive
|
98
|
+
def stop
|
99
|
+
@thread&.kill if @thread&.alive?
|
100
|
+
@thread = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def receive_headers(socket)
|
106
|
+
headers = {}
|
107
|
+
while (request = socket.gets) && (request.chomp.length > 0)
|
108
|
+
key, val = request.chomp.split(': ')
|
109
|
+
headers[key] = val
|
110
|
+
$logger.debug "Received #{headers.size} headers"
|
111
|
+
end
|
112
|
+
headers
|
113
|
+
end
|
114
|
+
|
115
|
+
def receive_data(socket, body_length)
|
116
|
+
read_length = body_length.to_i < max_received_size ? body_length.to_i : max_received_size
|
117
|
+
$logger.info "Reading #{read_length} bytes"
|
118
|
+
socket.read(read_length)
|
119
|
+
end
|
120
|
+
|
121
|
+
def end_connection(socket)
|
122
|
+
$logger.info "Responding with: #{response}"
|
123
|
+
# Unlikely to be used, but replicates pipeline response
|
124
|
+
socket.print response
|
125
|
+
socket.close
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/maze/timers.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Maze
|
2
|
+
|
3
|
+
# A simple run/stop timer
|
4
|
+
class Timer
|
5
|
+
attr_accessor :total
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@total = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def time(&block)
|
12
|
+
start = Time.now
|
13
|
+
|
14
|
+
block.call
|
15
|
+
ensure
|
16
|
+
@total += Time.now - start
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset
|
20
|
+
@total = 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Stores a collection of timers
|
25
|
+
class Timers
|
26
|
+
def initialize
|
27
|
+
@timers = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(name)
|
31
|
+
timer = Timer.new
|
32
|
+
@timers[name] = timer
|
33
|
+
timer
|
34
|
+
end
|
35
|
+
|
36
|
+
def get(name)
|
37
|
+
@timers[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
@timers.size
|
42
|
+
end
|
43
|
+
|
44
|
+
def report
|
45
|
+
$logger.info 'Timer totals:'
|
46
|
+
@timers.sort.each do |name, timer|
|
47
|
+
$logger.info " #{name}: #{timer.total}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/maze/wait.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
# Allows repeated attempts at something, until it is successful or the timeout
|
5
|
+
# is exceed
|
6
|
+
class Wait
|
7
|
+
# @param interval [Numeric] Optional. The time to sleep between attempts
|
8
|
+
# @param timeout [Numeric] The amount of time to spend on attempts before giving up
|
9
|
+
def initialize(interval: 0.1, timeout:)
|
10
|
+
raise "Interval must be greater than zero, got '#{interval}'" unless interval > 0
|
11
|
+
raise "Timeout (#{timeout}) must be greater than interval (#{interval})" unless timeout > interval
|
12
|
+
|
13
|
+
@interval = interval
|
14
|
+
@max_attempts = timeout / interval
|
15
|
+
end
|
16
|
+
|
17
|
+
# Wait until the given block succeeds (returns a truthy value) or the
|
18
|
+
# timeout is exceeded
|
19
|
+
#
|
20
|
+
# @return [Object] The last value returned by the block
|
21
|
+
def until(&block)
|
22
|
+
success = false
|
23
|
+
attempts = 0
|
24
|
+
|
25
|
+
until success || attempts >= @max_attempts do
|
26
|
+
attempts += 1
|
27
|
+
success = block.call
|
28
|
+
|
29
|
+
sleep @interval unless success
|
30
|
+
end
|
31
|
+
|
32
|
+
success
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/maze.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'maze/configuration'
|
4
|
+
require_relative 'maze/hooks/hooks'
|
5
|
+
require_relative 'maze/timers'
|
6
|
+
|
7
|
+
# Glues the various parts of MazeRunner together that need to be accessed globally,
|
8
|
+
# providing an alternative to the proliferation of global variables or singletons.
|
9
|
+
module Maze
|
10
|
+
VERSION = '6.27.0'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :check, :driver, :internal_hooks, :mode, :start_time, :dynamic_retry
|
14
|
+
|
15
|
+
def config
|
16
|
+
@config ||= Maze::Configuration.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def hooks
|
20
|
+
@hooks ||= Maze::Hooks::Hooks.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def timers
|
24
|
+
@timers ||= Maze::Timers.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|