bugsnag-maze-runner 8.1.4 → 8.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6135a5d1343dad9aec3ede4d5c400564c10cccb5fbc9ef894f4b99d6432a5324
4
- data.tar.gz: 35ac776b71e1a4a0d15baa74b6683aae314c1957a926316c25645785ba8ccd4c
3
+ metadata.gz: 02b1bd719cc437d684670a164059b1db11b6b86e10de81348e1027b74b1a138d
4
+ data.tar.gz: f5341e638425082222c5ca8462e4e98cce7a00a084351a91e746ebb07fc0d23e
5
5
  SHA512:
6
- metadata.gz: 1ff8c709e2e39927ae7a2290a50b9a0e45febfcd35228d2158e498fd2d9e2e357220f6d67b929d848aa0732173ed4e54774a7e8818553ef24be23c3bf5a0b664
7
- data.tar.gz: 9d9ed17d9e8dd7b9f84eb29bd040863cd275398bb83c96b29314159d202efc8b0df1c4923f7dfc5161193f905d94fdab9615111eb5871f817f0ff940c99c808d
6
+ metadata.gz: 683441571c5eb37acffc60787acaa1f697b9e20fe5af95ebb45abe52682ac0507b2ee1f4b55bc28ea0de7eb7587c5f829ee12bcde75482a818cee3be34cbafe5
7
+ data.tar.gz: 8832b0b1684e380449cc3aac2fecf6b2ae47195761a07ac6d1fdc469e6bd176ab89a068726e259dd63f3137af80d57b2542120c8e41b779afe211e75ac4c1470
data/bin/maze-runner CHANGED
@@ -9,6 +9,7 @@ require_relative '../lib/maze'
9
9
 
10
10
  require_relative '../lib/maze/appium_server'
11
11
  require_relative '../lib/maze/api/appium/file_manager'
12
+ require_relative '../lib/maze/api/cucumber/scenario'
12
13
  require_relative '../lib/maze/bugsnag_config'
13
14
  require_relative '../lib/maze/client/bb_api_client'
14
15
  require_relative '../lib/maze/client/bb_client_utils'
@@ -0,0 +1,8 @@
1
+ # @!group Deprecated steps
2
+
3
+ # Waits for a given number of spans to be received, which may be spread across one or more trace requests.
4
+ #
5
+ # @step_input span_count [Integer] The number of spans to wait for
6
+ When('I wait for {int} span(s)') do |span_count|
7
+ assert_received_spans Maze::Server.list_for('traces'), span_count
8
+ end
@@ -8,7 +8,7 @@ require_relative '../../maze/wait'
8
8
 
9
9
  # @!group Request assertion steps
10
10
 
11
- def assert_received_requests(request_count, list, list_name, precise = true)
11
+ def assert_received_requests(request_count, list, list_name, precise = true, maximum = nil)
12
12
  timeout = Maze.config.receive_requests_wait
13
13
  # Interval set to 0.5s to make it more likely to detect erroneous extra requests,
14
14
  # without impacting overall speed too much
@@ -45,6 +45,7 @@ def assert_received_requests(request_count, list, list_name, precise = true)
45
45
  Maze.check.equal(request_count, list.size_remaining, "#{list.size_remaining} #{list_name} received")
46
46
  else
47
47
  Maze.check.operator(request_count, :<=, list.size_remaining, "#{list.size_remaining} #{list_name} received")
48
+ Maze.check.operator(maximum, :>=, list.size_remaining, "#{list.size_remaining} #{list_name} received") unless maximum.nil?
48
49
  end
49
50
  end
50
51
 
@@ -102,6 +103,16 @@ Then('I have received at least {int} {request_type}') do |min_received, request_
102
103
  Maze.check.operator(list.size_remaining, :>=, min_received, "Actually received #{list.size_remaining} #{request_type} requests")
103
104
  end
104
105
 
106
+ # Verify that an amount of requests within a range have been received
107
+ #
108
+ # @step_input min_received [Integer] The minimum amount of requests required to pass
109
+ # @step_input max_received [Integer] The maximum amount of requests before failure
110
+ # @step_input request_type [String] The type of request (error, session, build, etc)
111
+ Then('I wait to receive between {int} and {int} {request_type}') do |min_received, max_received, request_type|
112
+ list = Maze::Server.list_for(request_type)
113
+ assert_received_requests min_received, list, request_type, false, max_received
114
+ end
115
+
105
116
  # Assert that the test Server hasn't received any requests - of a specific, or any, type.
106
117
  #
107
118
  # @step_input request_type [String] The type of request ('error', 'session', 'trace', sampling request', etc)
@@ -3,8 +3,25 @@
3
3
  # Waits for a given number of spans to be received, which may be spread across one or more trace requests.
4
4
  #
5
5
  # @step_input span_count [Integer] The number of spans to wait for
6
- When('I wait for {int} span(s)') do |span_count|
7
- assert_received_spans span_count, Maze::Server.list_for('traces')
6
+ Then('I wait to receive {int} span(s)') do |span_count|
7
+ assert_received_span_count Maze::Server.list_for('traces'), span_count
8
+ end
9
+
10
+ # Waits for a minimum number of spans to be received, which may be spread across one or more trace requests.
11
+ # If more spans than requested are received, this step will still pass.
12
+ #
13
+ # @step_input span_min [Integer] The minimum number of spans to wait for
14
+ Then('I wait to receive at least {int} span(s)') do |span_min|
15
+ assert_received_minimum_span_count Maze::Server.list_for('traces'), span_min
16
+ end
17
+
18
+ # Waits for a minimum number of spans to be received, which may be spread across one or more trace requests.
19
+ # If more spans than the maximum requested number of spans are received, this step will fail.
20
+ #
21
+ # @step_input span_min [Integer] The minimum number of spans to wait for
22
+ # @step_input span_max [Integer] The maximum number of spans to receive before failure
23
+ Then('I wait to receive between {int} and {int} span(s)') do |span_min, span_max|
24
+ assert_received_ranged_span_count Maze::Server.list_for('traces'), span_min, span_max
8
25
  end
9
26
 
10
27
  Then('I should have received no spans') do
@@ -227,16 +244,28 @@ def attribute_value_matches?(attribute_value, expected_type, expected_value)
227
244
  end
228
245
  end
229
246
 
230
- def assert_received_spans(span_count, list)
247
+ def assert_received_span_count(list, count)
248
+ assert_received_spans(list, count, count)
249
+ end
250
+
251
+ def assert_received_minimum_span_count(list, minimum)
252
+ assert_received_spans(list, minimum)
253
+ end
254
+
255
+ def assert_received_ranged_span_count(list, minimum, maximum)
256
+ assert_received_spans(list, minimum, maximum)
257
+ end
258
+
259
+ def assert_received_spans(list, min_received, max_received = nil)
231
260
  timeout = Maze.config.receive_requests_wait
232
261
  wait = Maze::Wait.new(timeout: timeout)
233
262
 
234
- received = wait.until { spans_from_request_list(list).size >= span_count }
263
+ received = wait.until { spans_from_request_list(list).size >= min_received }
235
264
  received_count = spans_from_request_list(list).size
236
265
 
237
266
  unless received
238
267
  raise Test::Unit::AssertionFailedError.new <<-MESSAGE
239
- Expected #{span_count} spans but received #{received_count} within the #{timeout}s timeout.
268
+ Expected #{min_received} spans but received #{received_count} within the #{timeout}s timeout.
240
269
  This could indicate that:
241
270
  - Bugsnag crashed with a fatal error.
242
271
  - Bugsnag did not make the requests that it should have done.
@@ -246,7 +275,7 @@ def assert_received_spans(span_count, list)
246
275
  MESSAGE
247
276
  end
248
277
 
249
- Maze.check.operator(span_count, :<=, received_count, "#{received_count} spans received")
278
+ Maze.check.operator(max_received, :>=, received_count, "#{received_count} spans received") if max_received
250
279
 
251
280
  Maze::Schemas::Validator.verify_against_schema(list, 'trace')
252
281
  Maze::Schemas::Validator.validate_payload_elements(list, 'trace')
@@ -30,12 +30,14 @@ BeforeAll do
30
30
  end
31
31
  $logger.info "Running in #{Maze.mode.to_s} mode"
32
32
 
33
- # Clear out maze_output folder
33
+ # Clear out maze_output folder and zip
34
34
  maze_output = Dir.glob(File.join(Dir.pwd, 'maze_output', '*'))
35
35
  if Maze.config.file_log && !maze_output.empty?
36
36
  maze_output.each { |path| $logger.info "Clearing contents of #{path}" }
37
37
  FileUtils.rm_rf(maze_output)
38
38
  end
39
+ maze_output_zip = Dir.glob(File.join(Dir.pwd, 'maze_output.zip'))
40
+ FileUtils.rm_rf(maze_output_zip)
39
41
 
40
42
  # Record the local server starting time
41
43
  Maze.start_time = Time.now.strftime('%Y-%m-%d %H:%M:%S')
@@ -94,6 +96,8 @@ end
94
96
 
95
97
  # Before each scenario
96
98
  Before do |scenario|
99
+ Maze.scenario = Maze::Api::Cucumber::Scenario.new(scenario)
100
+
97
101
  # Default to no dynamic try
98
102
  Maze.dynamic_retry = false
99
103
 
@@ -193,14 +197,20 @@ def output_received_requests(request_type)
193
197
  end
194
198
  end
195
199
 
196
- # Check for invalid requests after each scenario. This is its own hook as failing a scenario raises an exception
197
- # and we need the logic in the other After hook to be performed.
200
+ # Check for invalid requests after each scenario. This is its own hook as failing a scenario (which
201
+ # Maze.scenario.complete may invoke) raises an exception and we need the logic in the other After hook to be performed.
202
+ #
198
203
  # Furthermore, this hook should appear after the general hook as they are executed in reverse order by Cucumber.
199
204
  After do |scenario|
205
+ # Call any pre_complete hooks registered by the client
206
+ Maze.hooks.call_pre_complete scenario
207
+
200
208
  unless Maze::Server.invalid_requests.size_all == 0
201
209
  msg = "#{Maze::Server.invalid_requests.size_all} invalid request(s) received during scenario"
202
- scenario.fail msg
210
+ Maze.scenario.mark_as_failed msg
203
211
  end
212
+
213
+ Maze.scenario.complete
204
214
  end
205
215
 
206
216
  # Test all requests against schemas or extra validation rules. These will only run if the schema/validation is
@@ -214,6 +224,14 @@ end
214
224
 
215
225
  # After all tests
216
226
  AfterAll do
227
+ maze_output = File.join(Dir.pwd, 'maze_output')
228
+ maze_output_zip = File.join(Dir.pwd, 'maze_output.zip')
229
+ # zip a folder with files and subfolders
230
+ Zip::File.open(maze_output_zip, Zip::File::CREATE) do |zipfile|
231
+ Dir["#{maze_output}/**/**"].each do |file|
232
+ zipfile.add(file.sub(Dir.pwd + '/', ''), file)
233
+ end
234
+ end
217
235
 
218
236
  metrics = Maze::MetricsProcessor.new(Maze::Server.metrics)
219
237
  metrics.process
@@ -0,0 +1,25 @@
1
+ module Maze
2
+ module Api
3
+ module Cucumber
4
+ # An abstraction for the underlying Cucumber scenarios
5
+ class Scenario
6
+
7
+ # @param scenario The underlying Cucumber scenario
8
+ def initialize(scenario)
9
+ @scenario = scenario
10
+ @fail_override = false
11
+ @fail_override_reason = nil
12
+ end
13
+
14
+ def mark_as_failed(reason)
15
+ @fail_override = true
16
+ @fail_override_reason = reason
17
+ end
18
+
19
+ def complete
20
+ @scenario.fail(@fail_override_reason) if @fail_override
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -108,7 +108,6 @@ module Maze
108
108
  add_android 'Samsung Galaxy A8', '7.1', hash # ANDROID_7_1_SAMSUNG_GALAXY_A8
109
109
  add_android 'Samsung Galaxy Note 8', '7.1', hash # ANDROID_7_1_SAMSUNG_GALAXY_NOTE_8
110
110
  add_android 'Samsung Galaxy S8', '7.0', hash # ANDROID_7_0_SAMSUNG_GALAXY_S8
111
- add_android 'Samsung Galaxy S8 Plus', '7.0', hash # ANDROID_7_0_SAMSUNG_GALAXY_S8_PLUS
112
111
 
113
112
  # Specific iOS devices
114
113
  add_ios 'iPhone 14 Plus', '16.0', hash # IOS_16_0_IPHONE_14_PLUS
@@ -161,7 +161,7 @@ android_6:
161
161
 
162
162
  android_7:
163
163
  browserName: "Android Browser"
164
- device: "Samsung Galaxy S8 Plus"
164
+ device: "Samsung Galaxy S8"
165
165
  os: "android"
166
166
  osVersion: "7.0"
167
167
  realMobile: true
@@ -9,6 +9,7 @@ module Maze
9
9
  def initialize
10
10
  @before_all = []
11
11
  @before = []
12
+ @pre_complete = []
12
13
  @after = []
13
14
  end
14
15
 
@@ -22,7 +23,13 @@ module Maze
22
23
  @before << block
23
24
  end
24
25
 
25
- # Register blocks to be called from a Cucumber After hook (before MazeRunner does everything it needs to)
26
+ # Register blocks to be called from a Cucumber After hook (before the scenario is completed)
27
+ def pre_complete(&block)
28
+ @pre_complete << block
29
+ end
30
+
31
+ # Register blocks to be called from a Cucumber After hook (after the scenario is completed but before MazeRunner
32
+ # does everything it needs to between scenarios)
26
33
  def after(&block)
27
34
  @after << block
28
35
  end
@@ -38,6 +45,12 @@ module Maze
38
45
  @before.each { |block| block.call(scenario) }
39
46
  end
40
47
 
48
+ # For MazeRunner use only, call the registered pre-complete blocks
49
+ # @param scenario The current Cucumber scenario
50
+ def call_pre_complete(scenario)
51
+ @pre_complete.each { |block| block.call(scenario) }
52
+ end
53
+
41
54
  # For MazeRunner use only, call the registered After blocks
42
55
  # @param scenario The current Cucumber scenario
43
56
  def call_after(scenario)
@@ -50,7 +63,7 @@ module Maze
50
63
  def before_all; end
51
64
 
52
65
  def before(_scenario); end
53
-
66
+ def pre_complete(_scenario); end
54
67
  def after(_scenario); end
55
68
 
56
69
  def after_all; end
@@ -5,6 +5,11 @@ require_relative '../helper'
5
5
  module Maze
6
6
  module Schemas
7
7
 
8
+ HEX_STRING_16 = '^[A-Fa-f0-9]{16}$'
9
+ HEX_STRING_32 = '^[A-Fa-f0-9]{32}$'
10
+ SAMPLING_HEADER_ENTRY = '((1(.0)?|0(\.[0-9]+)?):[0-9]+)'
11
+ SAMPLING_HEADER = "^#{SAMPLING_HEADER_ENTRY}(;#{SAMPLING_HEADER_ENTRY})*$"
12
+
8
13
  # Contains a set of pre-defined validations for ensuring traces are correct
9
14
  class TraceValidator
10
15
 
@@ -15,9 +20,10 @@ module Maze
15
20
 
16
21
  # Creates the validator
17
22
  #
18
- # @param body [Hash] The body of the trace to validate
19
- def initialize(body)
20
- @body = body
23
+ # @param request [Hash] The trace request to validate
24
+ def initialize(request)
25
+ @headers = request[:request].header
26
+ @body = request[:body]
21
27
  @success = nil
22
28
  @errors = []
23
29
  end
@@ -26,8 +32,9 @@ module Maze
26
32
  def validate
27
33
  @success = true
28
34
 
29
- regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.spanId', '^[A-Fa-f0-9]{16}$')
30
- regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.traceId', '^[A-Fa-f0-9]{32}$')
35
+ validate_headers
36
+ regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.spanId', HEX_STRING_16)
37
+ regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.traceId', HEX_STRING_32)
31
38
  element_int_in_range('resourceSpans.0.scopeSpans.0.spans.0.kind', 0..5)
32
39
  regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.startTimeUnixNano', '^[0-9]+$')
33
40
  regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.endTimeUnixNano', '^[0-9]+$')
@@ -40,6 +47,49 @@ module Maze
40
47
  )
41
48
  end
42
49
 
50
+ def validate_header(name)
51
+ value = @headers[name]
52
+ if value.nil? || value.size > 1
53
+ @errors << "Expected exactly one value for header #{name}, received #{value || 'nil'}"
54
+ else
55
+ yield value[0]
56
+ end
57
+ end
58
+
59
+ # Checks that the required headers are present and correct
60
+ def validate_headers
61
+ # API key
62
+ validate_header('bugsnag-api-key') do |api_key|
63
+ expected = Regexp.new(HEX_STRING_32)
64
+ unless expected.match(api_key)
65
+ @success = false
66
+ @errors << "bugsnag-api-key header was expected to match the regex '#{HEX_STRING_32}', but was '#{api_key}'"
67
+ end
68
+ end
69
+
70
+ # Bugsnag-Sent-at
71
+ validate_header('bugsnag-sent-at') do |date|
72
+ begin
73
+ Date.iso8601(date)
74
+ rescue Date::Error
75
+ @success = false
76
+ @errors << "bugsnag-sent-at header was expected to be an IOS 8601 date, but was '#{date}'"
77
+ end
78
+ end
79
+
80
+ # Bugsnag-Span-Sampling
81
+ # of the format x:y where x is a decimal between 0 and 1 (inclusive) and y is the number of spans in the batch (if possible at this stage - we could weaken this if necessary)
82
+ validate_header('bugsnag-span-sampling') do |sampling|
83
+ begin
84
+ expected = Regexp.new(SAMPLING_HEADER)
85
+ unless expected.match(sampling)
86
+ @success = false
87
+ @errors << "bugsnag-span-sampling header was expected to match the regex '#{SAMPLING_HEADER}', but was '#{sampling}'"
88
+ end
89
+ end
90
+ end
91
+ end
92
+
43
93
  def regex_comparison(path, regex)
44
94
  element_value = Maze::Helper.read_key_path(@body, path)
45
95
  expected = Regexp.new(regex)
@@ -49,7 +49,7 @@ module Maze
49
49
 
50
50
  if validator_class
51
51
  validators = list.all.map do |request|
52
- validator = validator_class.new(request[:body])
52
+ validator = validator_class.new(request)
53
53
  validator.validate
54
54
  validator
55
55
  end
data/lib/maze/server.rb CHANGED
@@ -215,9 +215,12 @@ module Maze
215
215
  server.mount '/sessions', Servlets::Servlet, :sessions
216
216
  server.mount '/builds', Servlets::Servlet, :builds
217
217
  server.mount '/uploads', Servlets::Servlet, :uploads
218
- server.mount '/sourcemap', Servlets::Servlet, :sourcemaps
219
218
  server.mount '/traces', Servlets::TraceServlet, :traces, Maze::Schemas::TRACE_SCHEMA
219
+ server.mount '/sourcemap', Servlets::Servlet, :sourcemaps
220
220
  server.mount '/react-native-source-map', Servlets::Servlet, :sourcemaps
221
+ server.mount '/dart-symbol', Servlets::Servlet, :sourcemaps
222
+ server.mount '/ndk-symbol', Servlets::Servlet, :sourcemaps
223
+ server.mount '/proguard', Servlets::Servlet, :sourcemaps
221
224
  server.mount '/command', Servlets::CommandServlet
222
225
  server.mount '/logs', Servlets::LogServlet
223
226
  server.mount '/metrics', Servlets::Servlet, :metrics
@@ -256,6 +259,7 @@ module Maze
256
259
  set_sampling_probability_generator(Maze::Generator.new [DEFAULT_SAMPLING_PROBABILITY].cycle)
257
260
 
258
261
  # Clear request lists
262
+ commands.clear
259
263
  errors.clear
260
264
  sessions.clear
261
265
  builds.clear
data/lib/maze.rb CHANGED
@@ -7,11 +7,11 @@ require_relative 'maze/timers'
7
7
  # Glues the various parts of MazeRunner together that need to be accessed globally,
8
8
  # providing an alternative to the proliferation of global variables or singletons.
9
9
  module Maze
10
- VERSION = '8.1.4'
10
+ VERSION = '8.3.0'
11
11
 
12
12
  class << self
13
13
  attr_accessor :check, :driver, :internal_hooks, :mode, :start_time, :dynamic_retry, :public_address,
14
- :public_document_server_address, :run_uuid
14
+ :public_document_server_address, :run_uuid, :scenario
15
15
 
16
16
  def config
17
17
  @config ||= Maze::Configuration.new
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugsnag-maze-runner
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.4
4
+ version: 8.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Kirkland
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-05 00:00:00.000000000 Z
11
+ date: 2023-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cucumber
@@ -347,6 +347,7 @@ files:
347
347
  - lib/features/steps/breadcrumb_steps.rb
348
348
  - lib/features/steps/browser_steps.rb
349
349
  - lib/features/steps/build_api_steps.rb
350
+ - lib/features/steps/deprecated_steps.rb
350
351
  - lib/features/steps/document_server_steps.rb
351
352
  - lib/features/steps/error_reporting_steps.rb
352
353
  - lib/features/steps/feature_flag_steps.rb
@@ -367,6 +368,7 @@ files:
367
368
  - lib/features/support/internal_hooks.rb
368
369
  - lib/maze.rb
369
370
  - lib/maze/api/appium/file_manager.rb
371
+ - lib/maze/api/cucumber/scenario.rb
370
372
  - lib/maze/appium_server.rb
371
373
  - lib/maze/assertions/request_set_assertions.rb
372
374
  - lib/maze/aws/sam.rb
@@ -445,11 +447,11 @@ files:
445
447
  - lib/maze/wait.rb
446
448
  - lib/utils/deep_merge.rb
447
449
  - lib/utils/selenium_money_patch.rb
448
- homepage:
450
+ homepage:
449
451
  licenses:
450
452
  - MIT
451
453
  metadata: {}
452
- post_install_message:
454
+ post_install_message:
453
455
  rdoc_options: []
454
456
  require_paths:
455
457
  - lib
@@ -465,7 +467,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
465
467
  version: '0'
466
468
  requirements: []
467
469
  rubygems_version: 3.1.6
468
- signing_key:
470
+ signing_key:
469
471
  specification_version: 4
470
472
  summary: Bugsnag API request validation harness
471
473
  test_files: []