bugsnag-maze-runner 6.27.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.
- 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
|