bugsnag-maze-runner 9.13.1 → 9.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9aca9b6e4f9900613459a52fb7f4e25e64019c5bd6faf93df90e7853696c2f7
4
- data.tar.gz: 1bacf3c83498d35cf2a02d095636996d2242ec071fb59fd9adcaccdfce4c1eac
3
+ metadata.gz: 4ce5f9ee2bd12d032b5255c2eb7a37e80b81733ba14d8b8245a80f10c6f97a9b
4
+ data.tar.gz: df2943d57544c8815b128ee139e4fc8b6756a0c686e049e25d25887432018936
5
5
  SHA512:
6
- metadata.gz: 7e7c0548948485535717037cc2a7fd713b47fc0c068bc05b5af1a10f644c785b7d7d8e34c3ec460a2780eb52929af01979a9a54b6cf2f702a75c1e9dd3c1411c
7
- data.tar.gz: 648fd3aae9d6f85e856739ff32e2bf4a4c450ad7a69a9d963e3b6f570ec1dcaf39433afa5f2d9ff1116c0280cc6401c366cf805f26eba28b7b29cc2bc70a57bb
6
+ metadata.gz: 9fb38d691fb4d17246ea742100387a1c5fd1a6da70613e7f60eba9f1981c82085faf03668c85c4fff3c21f97da3d21dcfc727fae6b64e6dad46b740da71751e8
7
+ data.tar.gz: 39486b860cd75c00a9fc537f293a4e838a40f1946de9b83cba25ad332c0d3008724188461ea167116f3191d3a0c961435af39f06b53db20f3508c7fb01bf12a6
data/bin/maze-runner CHANGED
@@ -65,8 +65,6 @@ require_relative '../lib/maze/server'
65
65
 
66
66
  require_relative '../lib/maze/assertions/request_set_assertions'
67
67
 
68
- require_relative '../lib/maze/schemas/trace_schema'
69
- require_relative '../lib/maze/schemas/trace_validator'
70
68
  require_relative '../lib/maze/schemas/validator'
71
69
 
72
70
  require_relative '../lib/maze/store'
@@ -103,6 +101,25 @@ require_relative '../lib/utils/selenium_money_patch'
103
101
  # Encapsulates the MazeRunner entry point
104
102
  class MazeRunnerEntry
105
103
 
104
+ def read_options_file(filename)
105
+ return unless File.exist?(filename)
106
+
107
+ $logger.info "Reading command line options from #{filename}"
108
+ File.readlines(filename).each do |line|
109
+ line.strip!
110
+ next if line.empty? || line.start_with?('#')
111
+ @args << line
112
+ end
113
+ end
114
+
115
+ # Loads options from config files
116
+ def load_options_from_files
117
+ all_file = File.join('features', 'support', 'maze.all.cfg')
118
+ buildkite_file = File.join('features', 'support', 'maze.buildkite.cfg')
119
+ read_options_file(all_file)
120
+ read_options_file(buildkite_file) if ENV['BUILDKITE']
121
+ end
122
+
106
123
  # Removes Maze Runner specific args from the array, as these will cause Cucumber to error.
107
124
  def remove_maze_runner_args
108
125
  Maze::Option.constants.each do |opt|
@@ -134,6 +151,13 @@ class MazeRunnerEntry
134
151
  @args << "--expand"
135
152
  end
136
153
 
154
+ # Check if we've set ENV['MAZE_NO_FAIL_FAST'] to override the fail fast behaviour
155
+ # And remove the --fail-fast option if it's set
156
+ if ENV['MAZE_NO_FAIL_FAST'] && @args.include?('--fail-fast')
157
+ @args = @args - ['--fail-fast']
158
+ $logger.info 'Suppressing --fail-fast option as MAZE_NO_FAIL_FAST is set.'
159
+ end
160
+
137
161
  # Load internal steps and helper functions
138
162
  load_dir = File.expand_path(File.dirname(File.dirname(__FILE__))).freeze
139
163
  paths = Dir.glob("#{load_dir}/lib/features/**/*.rb")
@@ -150,7 +174,8 @@ class MazeRunnerEntry
150
174
 
151
175
  # Parse args, processing any Maze Runner specific options
152
176
  @args = args.dup
153
- options = Maze::Option::Parser.parse args
177
+ load_options_from_files
178
+ options = Maze::Option::Parser.parse @args
154
179
 
155
180
  if options[Maze::Option::LIST_DEVICES]
156
181
  case options[Maze::Option::FARM].to_sym
@@ -317,7 +317,6 @@ def assert_received_spans(list, min_received, max_received = nil)
317
317
 
318
318
  Maze.check.operator(max_received, :>=, received_count, "#{received_count} spans received") if max_received
319
319
 
320
- Maze::Schemas::Validator.verify_against_schema(list, 'trace')
321
320
  Maze::Schemas::Validator.validate_payload_elements(list, 'trace')
322
321
  end
323
322
 
@@ -233,7 +233,6 @@ end
233
233
  # specified for the specific endpoint
234
234
  After do |scenario|
235
235
  ['error', 'session', 'build', 'trace'].each do |endpoint|
236
- Maze::Schemas::Validator.verify_against_schema(Maze::Server.list_for(endpoint), endpoint)
237
236
  Maze::Schemas::Validator.validate_payload_elements(Maze::Server.list_for(endpoint), endpoint)
238
237
  end
239
238
  end
@@ -77,16 +77,17 @@ module Maze
77
77
  'ANDROID_11' => make_android_hash('Google Pixel 4', '11.0'),
78
78
  'ANDROID_10' => make_android_hash('Google Pixel 4', '10.0'),
79
79
  'ANDROID_9' => make_android_hash('Google Pixel 3', '9.0'),
80
- 'ANDROID_8' => make_android_hash('Samsung Galaxy Note 9', '8.1'),
80
+ 'ANDROID_8' => make_android_hash('Samsung Galaxy S9', '8.0'),
81
81
  'ANDROID_7' => make_android_hash('Samsung Galaxy S8', '7.0'),
82
82
 
83
83
  # iOS devices
84
+ 'IOS_18' => make_ios_hash('iPhone 14', '18'),
84
85
  'IOS_17' => make_ios_hash('iPhone 15', '17'),
85
86
  'IOS_16' => make_ios_hash('iPhone 14', '16'),
86
87
  'IOS_15' => make_ios_hash('iPhone 13', '15'),
87
88
  'IOS_14' => make_ios_hash('iPhone 11', '14'),
88
89
  'IOS_13' => make_ios_hash('iPhone 11', '13'),
89
- 'IOS_12' => make_ios_hash('iPhone 8', '12'),
90
+ 'IOS_12' => make_ios_hash('iPhone XS', '12'),
90
91
  }
91
92
 
92
93
  # Specific Android devices
@@ -97,15 +98,8 @@ module Maze
97
98
  add_android 'Motorola Moto G9 Play', '10.0', hash # ANDROID_10_0_MOTOROLA_MOTO_G9_PLAY
98
99
  add_android 'OnePlus 8', '10.0', hash # ANDROID_10_0_ONEPLUS_8
99
100
 
100
- add_android 'Google Pixel 2', '9.0', hash # ANDROID_9_0_GOOGLE_PIXEL_2
101
- add_android 'Samsung Galaxy Note 9', '8.1', hash # ANDROID_8_1_SAMSUNG_GALAXY_NOTE_9
102
- add_android 'Samsung Galaxy Tab S4', '8.1', hash # ANDROID_8_1_SAMSUNG_GALAXY_TAB_S4
103
- add_android 'Samsung Galaxy Tab S3', '8.0', hash # ANDROID_8_0_SAMSUNG_GALAXY_TAB_S3
104
101
  add_android 'Samsung Galaxy S9', '8.0', hash # ANDROID_8_0_SAMSUNG_GALAXY_S9
105
- add_android 'Samsung Galaxy S9 Plus', '8.0', hash # ANDROID_8_0_SAMSUNG_GALAXY_S9_PLUS
106
102
 
107
- add_android 'Samsung Galaxy A8', '7.1', hash # ANDROID_7_1_SAMSUNG_GALAXY_A8
108
- add_android 'Samsung Galaxy Note 8', '7.1', hash # ANDROID_7_1_SAMSUNG_GALAXY_NOTE_8
109
103
  add_android 'Samsung Galaxy S8', '7.0', hash # ANDROID_7_0_SAMSUNG_GALAXY_S8
110
104
 
111
105
  # Specific iOS devices
@@ -14,6 +14,7 @@ module Maze
14
14
  self.android_app_files_directory = nil
15
15
  self.span_timestamp_validation = true
16
16
  self.unmanaged_traces_mode = false
17
+ self.client_mode_validation = true
17
18
  @legacy_driver = false
18
19
  end
19
20
 
@@ -83,6 +84,33 @@ module Maze
83
84
  # Enables unmanaged trace mode.
84
85
  attr_accessor :unmanaged_traces_mode
85
86
 
87
+ # Custom validators to use for a given endpoint
88
+ attr_reader :custom_validators
89
+
90
+ # Consumes a block that will be triggered when a request is received for a specific endpoint
91
+ # This will prevent any existing default validation from triggering.
92
+ #
93
+ # @param endpoint [String] The endpoint to validate
94
+ # @param validator [Proc] The block to run when a request is received
95
+ def add_validator(endpoint, &validator)
96
+ @custom_validators ||= {}
97
+ @custom_validators[endpoint] = validator
98
+ end
99
+
100
+ # Whether default validation should be skipped for a given endpoint
101
+ attr_reader :skipped_validators
102
+
103
+ # Sets whether to skip default validation for a given endpoint
104
+ #
105
+ # @param endpoint [String] The endpoint to skip default validation for
106
+ def skip_default_validation(endpoint)
107
+ @skipped_validators ||= {}
108
+ @skipped_validators[endpoint] = true
109
+ end
110
+
111
+ # Sets whether validation should be run in client mode
112
+ attr_accessor :client_mode_validation
113
+
86
114
  #
87
115
  # General appium configuration
88
116
  #
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helper'
4
+ require_relative 'validator_base'
5
+
6
+ module Maze
7
+ module Schemas
8
+ class ConfigValidator < ValidatorBase
9
+
10
+ attr_accessor :success
11
+ attr_accessor :errors
12
+ attr_reader :headers
13
+ attr_reader :body
14
+
15
+ def initialize(request, validation_block)
16
+ super(request)
17
+ @validation_block = validation_block
18
+ end
19
+
20
+ def validate
21
+ @success = true
22
+ @validation_block.call(self)
23
+ rescue => exception
24
+ @success = false
25
+ @errors << "A #{exception.class} occurred while running validation: #{exception.message}, \n #{exception.backtrace.join("\n")}"
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helper'
4
+ require_relative 'validator_base'
5
+
6
+ module Maze
7
+ module Schemas
8
+
9
+ # Contains a set of pre-defined validations for ensuring errors are correct
10
+ class ErrorValidator < ValidatorBase
11
+ end
12
+ end
13
+ end
@@ -1,84 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helper'
4
+ require_relative 'trace_schema'
5
+ require_relative 'validator_base'
4
6
 
5
7
  module Maze
6
8
  module Schemas
7
9
 
8
- HEX_STRING_16 = '^[A-Fa-f0-9]{16}$'
9
- HEX_STRING_32 = '^[A-Fa-f0-9]{32}$'
10
10
  SAMPLING_HEADER_ENTRY = '((1(.0)?|0(\.[0-9]+)?):[0-9]+)'
11
11
  SAMPLING_HEADER = "^#{SAMPLING_HEADER_ENTRY}(;#{SAMPLING_HEADER_ENTRY})*$"
12
- HOUR_TOLERANCE = 60 * 60 * 1000 * 1000 * 1000 # 1 hour in nanoseconds
13
12
 
14
13
  # Contains a set of pre-defined validations for ensuring traces are correct
15
- class TraceValidator
16
-
17
- # Whether the trace passed the validation, one of true, false, or nil (not run)
18
- # @returns [Boolean|nil] Whether the validation was successful
19
- attr_reader :success
20
- attr_reader :errors
21
-
22
- # Creates the validator
23
- #
24
- # @param request [Hash] The trace request to validate
25
- def initialize(request)
26
- @headers = request[:request].header
27
- @body = request[:body]
28
- @success = nil
29
- @errors = []
30
- end
31
-
14
+ class TraceValidator < ValidatorBase
32
15
  # Runs the validation against the trace given
33
16
  def validate
17
+ # The tests are being run
34
18
  @success = true
35
-
19
+ verify_against_schema
36
20
  validate_headers
37
21
  regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.spanId', HEX_STRING_16)
38
22
  regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.traceId', HEX_STRING_32)
39
23
  element_int_in_range('resourceSpans.0.scopeSpans.0.spans.0.kind', 0..5)
40
24
  regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.startTimeUnixNano', '^[0-9]+$')
41
25
  regex_comparison('resourceSpans.0.scopeSpans.0.spans.0.endTimeUnixNano', '^[0-9]+$')
42
- element_contains('resourceSpans.0.resource.attributes', 'device.id')
43
- each_element_contains('resourceSpans.0.scopeSpans.0.spans', 'attributes', 'bugsnag.sampling.p')
44
- element_contains('resourceSpans.0.resource.attributes', 'deployment.environment')
45
- element_contains('resourceSpans.0.resource.attributes', 'telemetry.sdk.name')
46
- element_contains('resourceSpans.0.resource.attributes', 'telemetry.sdk.version')
26
+ each_span_element_contains('resourceSpans.0.scopeSpans.0.spans', 'attributes', 'bugsnag.sampling.p')
27
+ span_element_contains('resourceSpans.0.resource.attributes', 'deployment.environment')
28
+ span_element_contains('resourceSpans.0.resource.attributes', 'telemetry.sdk.name')
29
+ span_element_contains('resourceSpans.0.resource.attributes', 'telemetry.sdk.version')
47
30
  validate_timestamp('resourceSpans.0.scopeSpans.0.spans.0.startTimeUnixNano', HOUR_TOLERANCE)
48
31
  validate_timestamp('resourceSpans.0.scopeSpans.0.spans.0.endTimeUnixNano', HOUR_TOLERANCE)
49
32
  element_a_greater_or_equal_element_b(
50
33
  'resourceSpans.0.scopeSpans.0.spans.0.endTimeUnixNano',
51
34
  'resourceSpans.0.scopeSpans.0.spans.0.startTimeUnixNano'
52
35
  )
53
- end
54
36
 
55
- def validate_timestamp(path, tolerance)
56
- return unless Maze.config.span_timestamp_validation
57
- timestamp = Maze::Helper.read_key_path(@body, path)
58
- unless timestamp.kind_of?(String)
59
- @success = false
60
- @errors << "Timestamp was expected to be a string, was '#{timestamp.class.name}'"
61
- return
62
- end
63
- parsed_timestamp = timestamp.to_i
64
- unless parsed_timestamp > 0
65
- @success = false
66
- @errors << "Timestamp was expected to be a positive integer, was '#{parsed_timestamp}'"
67
- return
68
- end
69
- time_in_nanos = Time.now.to_i * 1000000000
70
- unless (time_in_nanos - parsed_timestamp).abs < tolerance
71
- @success = false
72
- @errors << "Timestamp was expected to be within #{tolerance} nanoseconds of the current time (#{time_in_nanos}), was '#{parsed_timestamp}'"
37
+ if Maze.config.client_mode_validation
38
+ span_element_contains('resourceSpans.0.resource.attributes', 'device.id')
73
39
  end
74
40
  end
75
41
 
76
- def validate_header(name)
77
- value = @headers[name]
78
- if value.nil? || value.size > 1
79
- @errors << "Expected exactly one value for header #{name}, received #{value || 'nil'}"
80
- else
81
- yield value[0]
42
+ def verify_against_schema
43
+ if !@schema_errors.nil? && @schema_errors.size > 0
44
+ @success = false
45
+ @schema_errors.each do |error|
46
+ @errors << "#{JSONSchemer::Errors.pretty(error)}"
47
+ end
82
48
  end
83
49
  end
84
50
 
@@ -118,29 +84,7 @@ module Maze
118
84
  end
119
85
  end
120
86
 
121
- def regex_comparison(path, regex)
122
- element_value = Maze::Helper.read_key_path(@body, path)
123
- expected = Regexp.new(regex)
124
- unless expected.match(element_value)
125
- @success = false
126
- @errors << "Element '#{path}' was expected to match the regex '#{regex}', but was '#{element_value}'"
127
- end
128
- end
129
-
130
- def element_int_in_range(path, range)
131
- element_value = Maze::Helper.read_key_path(@body, path)
132
- if element_value.nil? || !element_value.kind_of?(Integer)
133
- @success = false
134
- @errors << "Element '#{path}' was expected to be an integer, was '#{element_value}'"
135
- return
136
- end
137
- unless range.include?(element_value)
138
- @success = false
139
- @errors << "Element '#{path}':'#{element_value}' was expected to be in the range '#{range}'"
140
- end
141
- end
142
-
143
- def element_contains(path, key_value, value_type=nil, possible_values=nil)
87
+ def span_element_contains(path, key_value, value_type=nil, possible_values=nil)
144
88
  container = Maze::Helper.read_key_path(@body, path)
145
89
  if container.nil? || !container.kind_of?(Array)
146
90
  @success = false
@@ -161,7 +105,7 @@ module Maze
161
105
  end
162
106
  end
163
107
 
164
- def each_element_contains(container_path, attribute_path, key_value)
108
+ def each_span_element_contains(container_path, attribute_path, key_value)
165
109
  container = Maze::Helper.read_key_path(@body, container_path)
166
110
  if container.nil? || !container.kind_of?(Array)
167
111
  @success = false
@@ -169,16 +113,7 @@ module Maze
169
113
  return
170
114
  end
171
115
  container.each_with_index do |_item, index|
172
- element_contains("#{container_path}.#{index}.#{attribute_path}", key_value)
173
- end
174
- end
175
-
176
- def element_a_greater_or_equal_element_b(path_a, path_b)
177
- element_a = Maze::Helper.read_key_path(@body, path_a)
178
- element_b = Maze::Helper.read_key_path(@body, path_b)
179
- unless element_a && element_b && element_a >= element_b
180
- @success = false
181
- @errors << "Element '#{path_a}':'#{element_a}' was expected to be greater than or equal to '#{path_b}':'#{element_b}'"
116
+ span_element_contains("#{container_path}.#{index}.#{attribute_path}", key_value)
182
117
  end
183
118
  end
184
119
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'config_validator'
4
+ require_relative 'error_validator'
5
+ require_relative 'trace_validator'
6
+
3
7
  module Maze
4
8
  module Schemas
5
9
 
@@ -7,56 +11,42 @@ module Maze
7
11
  class Validator
8
12
 
9
13
  class << self
10
-
11
- # Tests that payloads for a specific path have passed any schema checks implemented on receipt
12
- # Throws an AssertionFailedError with a list of issues on failure
13
- #
14
- # @param list [Array] An array of received requests
15
- # @param list_name [String] The name of the payload list for received requests
16
- def verify_against_schema(list, list_name)
17
- request_schema_results = list.all.map { |request| request[:schema_errors] }
18
- passed = true
19
- request_schema_results.each.with_index(1) do |schema_errors, index|
20
- next if schema_errors.nil?
21
- if schema_errors.size > 0
22
- passed = false
23
- $stdout.puts "\n"
24
- $stdout.puts "\e[31m--- #{list_name} #{index} failed validation:\e[0m"
25
- schema_errors.each do |error|
26
- $stdout.puts "\e[31m#{JSONSchemer::Errors.pretty(error)}\e[0m"
27
- end
28
- $stdout.puts "\n"
29
- end
30
- end
31
-
32
- unless passed
33
- raise Test::Unit::AssertionFailedError.new 'The received payloads did not match the endpoint schema. A full list of the errors can be found above'
34
- end
35
- end
36
-
37
14
  # Tests that payloads for a specific path pass any additional validation checks
38
15
  # Throws an AssertionFailedError with a list of issues on failure
39
16
  #
40
17
  # @param list [Array] An array of received requests
41
18
  # @param list_name [String] The name of the payload list for received requests
42
19
  def validate_payload_elements(list, list_name)
43
- validator_class = case list_name
44
- when 'trace', 'traces'
45
- Maze::Schemas::TraceValidator
20
+ # Test to see if a custom validator exists for the list
21
+ custom_validator = Maze.config.custom_validators&.key?(list_name)
22
+
23
+ if Maze.config.skipped_validators && Maze.config.skipped_validators[list_name]
24
+ validator_class = false
46
25
  else
47
- nil
26
+ validator_class = case list_name
27
+ when 'trace', 'traces'
28
+ Maze::Schemas::TraceValidator
29
+ when 'error', 'errors'
30
+ Maze::Schemas::ErrorValidator
31
+ else
32
+ nil
33
+ end
48
34
  end
49
35
 
50
- if validator_class
51
- validators = list.all.map do |request|
52
- validator = validator_class.new(request)
53
- validator.validate
54
- validator
55
- end
36
+ list_validators = list.all.map do |request|
37
+ payload_validators = []
38
+ payload_validators << Maze::Schemas::ConfigValidator.new(request, Maze.config.custom_validators[list_name]) if custom_validator
39
+ payload_validators << validator_class.new(request) if validator_class
40
+
41
+ payload_validators.each { |validator| validator.validate }
42
+ payload_validators
43
+ end
56
44
 
57
- return if validators.all? { |validator| validator.success }
58
- validators.each.with_index(1) do |validator, index|
45
+ failing = false
46
+ list_validators.each.with_index(1) do |validators, index|
47
+ validators.each do |validator|
59
48
  unless validator.success
49
+ failing = true
60
50
  $stdout.puts "\n"
61
51
  $stdout.puts "\e[31m--- #{list_name} #{index} failed validation with the following errors:\e[0m"
62
52
  validator.errors.each do |error|
@@ -65,8 +55,8 @@ module Maze
65
55
  $stdout.puts "\n"
66
56
  end
67
57
  end
68
- raise Test::Unit::AssertionFailedError.new("One or more #{list_name} payloads failed validation. A full list of the errors can be found above")
69
58
  end
59
+ raise Test::Unit::AssertionFailedError.new("One or more #{list_name} payloads failed validation. A full list of the errors can be found above") if failing
70
60
  end
71
61
  end
72
62
  end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maze
4
+ module Schemas
5
+ class ValidatorBase
6
+
7
+ HEX_STRING_16 = '^[A-Fa-f0-9]{16}$'
8
+ HEX_STRING_32 = '^[A-Fa-f0-9]{32}$'
9
+ HOUR_TOLERANCE = 60 * 60 * 1000 * 1000 * 1000 # 1 hour in nanoseconds
10
+
11
+ # Whether the payloads passed the validation, one of true, false, or nil (not run)
12
+ # @returns [Boolean|nil] Whether the validation was successful
13
+ attr_reader :success
14
+
15
+ # An array of error messages if the validation failed
16
+ # @returns [Array] The error messages
17
+ attr_reader :errors
18
+
19
+ # Creates the validator
20
+ #
21
+ # @param request [Hash] The trace request to validate
22
+ def initialize(request)
23
+ @headers = request[:request].header
24
+ @body = request[:body]
25
+ @schema_errors = request[:schema_errors]
26
+ @success = nil
27
+ @errors = []
28
+ end
29
+
30
+ def validate
31
+ # By default the validation will pass
32
+ @success = true
33
+ end
34
+
35
+ def element_has_value(path, value)
36
+ element = Maze::Helper.read_key_path(@body, path)
37
+ if element.nil? || element != value
38
+ @success = false
39
+ @errors << "Element '#{path}' was expected to be '#{value}', was '#{element}'"
40
+ end
41
+ end
42
+
43
+ def element_exists(path)
44
+ element = Maze::Helper.read_key_path(@body, path)
45
+ if element.nil?
46
+ @success = false
47
+ @errors << "Element '#{path}' was not found"
48
+ end
49
+ end
50
+
51
+ def each_element_exists(paths)
52
+ if paths.kind_of?(Array)
53
+ paths.each {|path| element_exists(path)}
54
+ else
55
+ $logger.warn("each_element_exists was called with a non-array value: '#{paths}'. Use element_exists instead.")
56
+ element_exists(paths)
57
+ end
58
+
59
+ end
60
+
61
+ def each_element_contains(container_path, path)
62
+ containers = Maze::Helper.read_key_path(@body, container_path)
63
+ containers.each_with_index do |container, index|
64
+ element = Maze::Helper.read_key_path(container, path)
65
+ if element.nil?
66
+ @success = false
67
+ @errors << "Required #{container_path} element #{path} was not present at index #{index}"
68
+ end
69
+ end
70
+ end
71
+
72
+ def each_event_contains(path)
73
+ each_element_contains('events', path)
74
+ end
75
+
76
+ def each_element_contains_each(container_path, paths)
77
+ paths.each { |path| each_element_contains(container_path, path) }
78
+ end
79
+
80
+ def each_event_contains_each(paths)
81
+ paths.each { |path| each_event_contains(path) }
82
+ end
83
+
84
+ def regex_comparison(path, regex)
85
+ element_value = Maze::Helper.read_key_path(@body, path)
86
+ expected = Regexp.new(regex)
87
+ unless expected.match(element_value)
88
+ @success = false
89
+ @errors << "Element '#{path}' was expected to match the regex '#{regex}', but was '#{element_value}'"
90
+ end
91
+ end
92
+
93
+ def element_int_in_range(path, range)
94
+ element_value = Maze::Helper.read_key_path(@body, path)
95
+ if element_value.nil? || !element_value.kind_of?(Integer)
96
+ @success = false
97
+ @errors << "Element '#{path}' was expected to be an integer, was '#{element_value}'"
98
+ return
99
+ end
100
+ unless range.include?(element_value)
101
+ @success = false
102
+ @errors << "Element '#{path}':'#{element_value}' was expected to be in the range '#{range}'"
103
+ end
104
+ end
105
+
106
+ def element_a_greater_or_equal_element_b(path_a, path_b)
107
+ element_a = Maze::Helper.read_key_path(@body, path_a)
108
+ element_b = Maze::Helper.read_key_path(@body, path_b)
109
+ unless element_a && element_b && element_a >= element_b
110
+ @success = false
111
+ @errors << "Element '#{path_a}':'#{element_a}' was expected to be greater than or equal to '#{path_b}':'#{element_b}'"
112
+ end
113
+ end
114
+
115
+ def validate_timestamp(path, tolerance)
116
+ return unless Maze.config.span_timestamp_validation
117
+ timestamp = Maze::Helper.read_key_path(@body, path)
118
+ unless timestamp.kind_of?(String)
119
+ @success = false
120
+ @errors << "Timestamp was expected to be a string, was '#{timestamp.class.name}'"
121
+ return
122
+ end
123
+ parsed_timestamp = timestamp.to_i
124
+ unless parsed_timestamp > 0
125
+ @success = false
126
+ @errors << "Timestamp was expected to be a positive integer, was '#{parsed_timestamp}'"
127
+ return
128
+ end
129
+ time_in_nanos = Time.now.to_i * 1000000000
130
+ unless (time_in_nanos - parsed_timestamp).abs < tolerance
131
+ @success = false
132
+ @errors << "Timestamp was expected to be within #{tolerance} nanoseconds of the current time (#{time_in_nanos}), was '#{parsed_timestamp}'"
133
+ end
134
+ end
135
+
136
+ def validate_header(name)
137
+ begin
138
+ value = @headers[name]
139
+ if value.nil? || value.size > 1
140
+ @success = false
141
+ @errors << "Expected exactly one value for header #{name}, received #{value || 'nil'}"
142
+ else
143
+ yield value[0]
144
+ end
145
+ rescue => e
146
+ @success = false
147
+ @errors << "Error validating header #{name} with value #{value}: #{e.message}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
data/lib/maze.rb CHANGED
@@ -7,7 +7,7 @@ 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 = '9.13.1'
10
+ VERSION = '9.15.0'
11
11
 
12
12
  class << self
13
13
  attr_accessor :check, :driver, :internal_hooks, :mode, :start_time, :dynamic_retry, :public_address,
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: 9.13.1
4
+ version: 9.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Kirkland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-30 00:00:00.000000000 Z
11
+ date: 2024-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cucumber
@@ -234,6 +234,62 @@ dependencies:
234
234
  - - "~>"
235
235
  - !ruby/object:Gem::Version
236
236
  version: 2.3.2
237
+ - !ruby/object:Gem::Dependency
238
+ name: ostruct
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: 0.6.0
244
+ type: :runtime
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: 0.6.0
251
+ - !ruby/object:Gem::Dependency
252
+ name: logger
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - "~>"
256
+ - !ruby/object:Gem::Version
257
+ version: '1.6'
258
+ type: :runtime
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - "~>"
263
+ - !ruby/object:Gem::Version
264
+ version: '1.6'
265
+ - !ruby/object:Gem::Dependency
266
+ name: base64
267
+ requirement: !ruby/object:Gem::Requirement
268
+ requirements:
269
+ - - "~>"
270
+ - !ruby/object:Gem::Version
271
+ version: 0.2.0
272
+ type: :runtime
273
+ prerelease: false
274
+ version_requirements: !ruby/object:Gem::Requirement
275
+ requirements:
276
+ - - "~>"
277
+ - !ruby/object:Gem::Version
278
+ version: 0.2.0
279
+ - !ruby/object:Gem::Dependency
280
+ name: bigdecimal
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - "~>"
284
+ - !ruby/object:Gem::Version
285
+ version: '3.1'
286
+ type: :runtime
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - "~>"
291
+ - !ruby/object:Gem::Version
292
+ version: '3.1'
237
293
  - !ruby/object:Gem::Dependency
238
294
  name: license_finder
239
295
  requirement: !ruby/object:Gem::Requirement
@@ -440,9 +496,12 @@ files:
440
496
  - lib/maze/retry_handler.rb
441
497
  - lib/maze/runner.rb
442
498
  - lib/maze/schemas/OtelTraceSchema.json
499
+ - lib/maze/schemas/config_validator.rb
500
+ - lib/maze/schemas/error_validator.rb
443
501
  - lib/maze/schemas/trace_schema.rb
444
502
  - lib/maze/schemas/trace_validator.rb
445
503
  - lib/maze/schemas/validator.rb
504
+ - lib/maze/schemas/validator_base.rb
446
505
  - lib/maze/server.rb
447
506
  - lib/maze/servlets/all_commands_servlet.rb
448
507
  - lib/maze/servlets/base_servlet.rb