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 +4 -4
- data/bin/maze-runner +28 -3
- data/lib/features/steps/trace_steps.rb +0 -1
- data/lib/features/support/internal_hooks.rb +0 -1
- data/lib/maze/client/appium/bs_devices.rb +3 -9
- data/lib/maze/configuration.rb +28 -0
- data/lib/maze/schemas/config_validator.rb +30 -0
- data/lib/maze/schemas/error_validator.rb +13 -0
- data/lib/maze/schemas/trace_validator.rb +20 -85
- data/lib/maze/schemas/validator.rb +30 -40
- data/lib/maze/schemas/validator_base.rb +152 -0
- data/lib/maze.rb +1 -1
- metadata +61 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ce5f9ee2bd12d032b5255c2eb7a37e80b81733ba14d8b8245a80f10c6f97a9b
|
4
|
+
data.tar.gz: df2943d57544c8815b128ee139e4fc8b6756a0c686e049e25d25887432018936
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
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
|
data/lib/maze/configuration.rb
CHANGED
@@ -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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
56
|
-
|
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
|
77
|
-
|
78
|
-
|
79
|
-
@
|
80
|
-
|
81
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
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.
|
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.
|
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-
|
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
|