sealights-rspec-agent 2.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5cc91027b052b89b83a7d22663733f1bf20ff0263b76e97e84d5978b993e62d0
4
+ data.tar.gz: d71970e5fab158eca4e44cdb1167a879ffdb82779b73dabfa542b474125c7030
5
+ SHA512:
6
+ metadata.gz: 27ee78793e7e80babf278a255cc1f36f54c07a5c573051f1348c8e8af64778aa5e1da055f8a4cd13dc4c29c1cc5ae87e061664b74466a963218fce8422b48a7f
7
+ data.tar.gz: 886188ade8f1b296b60bf3f413a552955063f9f5e2a956aaa357cd31f7974304e35a6adb5a774f37c904c7c0f7d80612ce1f225faccdcde8f8e21068374b0822
data/agent/config.rb ADDED
@@ -0,0 +1,120 @@
1
+ #
2
+ # This script contains code responsible for gathering the Agent's configuration.
3
+ #
4
+
5
+ require_relative './utils'
6
+
7
+ require 'json'
8
+ require 'jwt'
9
+
10
+ DEFAULT_TIA_DISABLED = 'false'
11
+ DEFAULT_TIA_POLLING_INTERVAL_SECS = 5
12
+ DEFAULT_TIA_POLLING_TIMEOUT_SECS = 60
13
+
14
+ # environment variables used by the agent
15
+ TOKEN_FILE_PATH_ENV_VAR = 'SL_TOKEN_FILE_PATH'
16
+ TOKEN_ENV_VAR = 'SL_TOKEN'
17
+ BUILD_SESSION_ID_ENV_VAR = 'SL_BUILD_SESSION_ID'
18
+ BUILD_SESSION_ID_PATH_ENV_VAR = 'SL_BUILD_SESSION_ID_PATH'
19
+ TEST_STAGE_ENV_VAR = 'SL_TEST_STAGE'
20
+ TIA_DISABLED_ENV_VAR = 'SL_TIA_DISABLED'
21
+ TIA_POLLING_INTERVAL_ENV_VAR = 'SL_TIA_POLL_INTERVAL'
22
+ TIA_POLLING_TIMEOUT_ENV_VAR = 'SL_TIA_POLL_TIMEOUT'
23
+
24
+ #
25
+ # This class represents the Agent's configuration. On initialization, the fields get populated based on environment
26
+ # variables, calls to the back-end server, and (optionally) files pointed to the by environment variables.
27
+ #
28
+ class AgentConfiguration
29
+ BUILD_SESSION_ENDPOINT = "/v2/agents/buildsession/"
30
+ TRUE_STRING = 'true'
31
+ SL_SERVER = 'x-sl-server'
32
+
33
+ attr_reader :build_session_id, :server, :customer_id, :test_stage, :tia_enabled, :jwt, :app_name, :branch, :build, :polling_timeout_secs, :polling_interval_secs
34
+
35
+ #
36
+ # An important side effect of AgentConfiguration's initialization is that it will set the value of `rest_client.jwt`.
37
+ #
38
+ def initialize(rest_client, env_reader = EnvironmentVariablesReader.new, file_reader = FileReader.new)
39
+ @env_vars = env_reader.read_vars
40
+ @file_reader = file_reader
41
+ @jwt = take_from_path_or_env(@env_vars, :tokenFile, :token)
42
+ @build_session_id = take_from_path_or_env(@env_vars, :buildSessionIdFile, :buildSessionId)
43
+ @server = server_from_token(@jwt)
44
+ rest_client.jwt = @jwt
45
+ build_session = JSON.parse(rest_client.get @server + BUILD_SESSION_ENDPOINT + @build_session_id)
46
+ @customer_id = build_session['customerId']
47
+ @app_name = build_session['appName']
48
+ @branch = build_session['branchName']
49
+ @build = build_session['buildName']
50
+ @test_stage = @env_vars[:testStage]
51
+ @tia_enabled = @env_vars.fetch(:tiaDisabled, DEFAULT_TIA_DISABLED) != TRUE_STRING
52
+
53
+ @polling_interval_secs = get_integer_from_env_vars(:pollingIntervalSecs, DEFAULT_TIA_POLLING_INTERVAL_SECS)
54
+ @polling_timeout_secs = get_integer_from_env_vars(:pollingTimeoutSecs, DEFAULT_TIA_POLLING_TIMEOUT_SECS)
55
+
56
+ info "RSpec agent config: #{pretty_fields(self, 'env_vars', 'file_reader')}"
57
+ rescue StandardError => e
58
+ error "Error while resolving agent configuration: #{e.message}"
59
+ raise(e)
60
+ end
61
+
62
+ private
63
+
64
+ def get_integer_from_env_vars(key, default_value)
65
+ Integer(@env_vars.fetch(key))
66
+ rescue
67
+ default_value
68
+ end
69
+
70
+ def take_from_path_or_env(env_vars, path_key, value_key)
71
+ path = env_vars[path_key]
72
+ @build_session_id = path.nil? ? env_vars[value_key] : read_value_from_file(path)
73
+ end
74
+
75
+ def read_value_from_file(path)
76
+ @file_reader.read(path).strip
77
+ end
78
+
79
+ def server_from_token(token)
80
+ JWT.decode(token, nil, false)[0].fetch(SL_SERVER)
81
+ end
82
+ end
83
+
84
+ class EnvironmentVariablesReader
85
+ def read_vars
86
+ env_vars = {
87
+ :tokenFile => ENV[TOKEN_FILE_PATH_ENV_VAR],
88
+ :token => ENV[TOKEN_ENV_VAR],
89
+ :buildSessionIdFile => ENV[BUILD_SESSION_ID_PATH_ENV_VAR],
90
+ :buildSessionId => ENV[BUILD_SESSION_ID_ENV_VAR],
91
+ :tiaDisabled => ENV[TIA_DISABLED_ENV_VAR],
92
+ :pollingIntervalSecs => ENV[TIA_POLLING_INTERVAL_ENV_VAR],
93
+ :pollingTimeoutSecs => ENV[TIA_POLLING_TIMEOUT_ENV_VAR],
94
+ :testStage => ENV.fetch(TEST_STAGE_ENV_VAR)
95
+ }
96
+
97
+ validate_exactly_one_is_set(TOKEN_FILE_PATH_ENV_VAR, env_vars[:tokenFile], TOKEN_ENV_VAR, env_vars[:token])
98
+ validate_exactly_one_is_set(BUILD_SESSION_ID_PATH_ENV_VAR, env_vars[:buildSessionIdFile], BUILD_SESSION_ID_ENV_VAR, env_vars[:buildSessionId])
99
+
100
+ env_vars
101
+ rescue StandardError => e
102
+ error "Error while resolving environment variables: #{e.message}."
103
+ raise(e)
104
+ end
105
+
106
+ private
107
+
108
+ def validate_exactly_one_is_set(first_name, first_value, second_name, second_value)
109
+ message = "Exactly one of the environment variables: '#{first_name}' or '#{second_name}' should be set."
110
+
111
+ # XORing to check if exactly one non-nil value
112
+ raise(KeyError.new(message)) unless first_value.nil? ^ second_value.nil?
113
+ end
114
+ end
115
+
116
+ class FileReader
117
+ def read(path)
118
+ File.read(path)
119
+ end
120
+ end
data/agent/listener.rb ADDED
@@ -0,0 +1,229 @@
1
+ #
2
+ # This script contains code for listening for test events and in turn sending notifications to the back-end.
3
+ #
4
+
5
+ require_relative './rest-client-wrapper'
6
+ require_relative './utils'
7
+
8
+ require 'json'
9
+ require 'securerandom'
10
+ require 'singleton'
11
+
12
+ #
13
+ # RSpecListener implements methods needed for subscription to RSpec::Core::Reporter. The messages from RSpec are then
14
+ # passed to the EventsDispatcher.
15
+ #
16
+ class RSpecListener
17
+ def initialize(http_client, server, customer_id, app_name, build, branch, test_stage, build_session_id, test_selection_status)
18
+ @events_handler = EventsDispatcher.new(http_client, server, customer_id, app_name, build, branch, test_stage, build_session_id, test_selection_status)
19
+ info "Started RSpec Agent for application name: '#{customer_id}', build: '#{branch}', build '#{build}', build session id: '#{build_session_id}', test selection status: '#{test_selection_status}'"
20
+ end
21
+
22
+ def start(notification)
23
+ @execution_id = SecureRandom.uuid
24
+ @events_handler.execution_started(@execution_id)
25
+ end
26
+
27
+ def stop(notification)
28
+ @events_handler.execution_ended(@execution_id)
29
+ @execution_id = nil
30
+ end
31
+
32
+ def example_started(notification)
33
+ TestIdTracker.instance.set_current_test_identifier(notification.example.full_description)
34
+ @events_handler.test_started(notification.example.full_description, notification.example.example_group.description, @execution_id)
35
+ end
36
+
37
+ def example_passed(notification)
38
+ @events_handler.test_passed(notification.example.full_description, notification.example.example_group.description, @execution_id, get_duration_millis(notification))
39
+ end
40
+
41
+ def example_failed(notification)
42
+ @events_handler.test_failed(notification.example.full_description, notification.example.example_group.description, @execution_id, get_duration_millis(notification))
43
+ end
44
+
45
+ def example_pending(notification)
46
+ @events_handler.test_skipped(notification.example.full_description, notification.example.example_group.description, @execution_id, get_duration_millis(notification))
47
+ end
48
+
49
+ def example_finished(notification)
50
+ TestIdTracker.instance.set_current_test_identifier(nil)
51
+ end
52
+
53
+ private
54
+
55
+ MILLIS_PER_SECOND = 1000
56
+
57
+ def get_duration_millis(notification)
58
+ notification.example.execution_result.run_time * MILLIS_PER_SECOND
59
+ end
60
+ end
61
+
62
+ #
63
+ # EventsDispatcher is used for sending the test events to the back-end. The events are aggregated into batches of 10.
64
+ #
65
+ class EventsDispatcher
66
+ RSPEC_FRAMEWORK_NAME = :RSpec
67
+ EVENTS_ENDPOINT = '/v2/agents/events'
68
+
69
+ PASSED_STATUS = :passed
70
+ FAILED_STATUS = :failed
71
+ SKIPPED_STATUS = :skipped
72
+
73
+ def initialize(http_client, server, customer_id, app_name, build, branch, test_stage, build_session_id, test_selection_status)
74
+ @http_client = http_client
75
+ @server = server
76
+ @customer_id = customer_id
77
+ @app_name = app_name
78
+ @build = build
79
+ @branch = branch
80
+ @test_stage = test_stage
81
+ @build_session_id = build_session_id
82
+ @test_selection_status = test_selection_status
83
+ @buffered_events = []
84
+
85
+ # adding an exit hook to make sure that any remaining events get sent in case of a problem
86
+ at_exit do
87
+ flush_events_buffer
88
+ end
89
+ end
90
+
91
+ def test_started(test_name, example_group, execution_id)
92
+ evt = create_test_event(:testStart, example_group, test_name, execution_id)
93
+ add_to_buffer evt
94
+ end
95
+
96
+ def test_passed(test_name, example_group, execution_id, duration)
97
+ evt = create_test_end_event(example_group, test_name, execution_id, PASSED_STATUS, duration)
98
+ add_to_buffer evt
99
+ end
100
+
101
+ def test_failed(test_name, example_group, execution_id, duration)
102
+ evt = create_test_end_event(example_group, test_name, execution_id, FAILED_STATUS, duration)
103
+ add_to_buffer evt
104
+ end
105
+
106
+ def test_skipped(test_name, example_group, execution_id, duration)
107
+ evt = create_test_end_event(example_group, test_name, execution_id, SKIPPED_STATUS, duration)
108
+ add_to_buffer evt
109
+ end
110
+
111
+ def execution_started(execution_id)
112
+ evt = create_basic_test_event(:executionIdStarted, execution_id)
113
+ add_to_buffer evt
114
+ end
115
+
116
+ def execution_ended(execution_id)
117
+ evt = create_basic_test_event(:executionIdEnded, execution_id)
118
+ add_to_buffer evt
119
+
120
+ flush_events_buffer
121
+ end
122
+
123
+ def flush_events_buffer
124
+ unless @buffered_events.empty?
125
+ send_buffered_events
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ EVENTS_BATCH_SIZE = 10
132
+ MILLIS_IN_SECOND = 1000
133
+
134
+ def add_to_buffer evt
135
+ @buffered_events.append(evt)
136
+
137
+ if @buffered_events.size >= EVENTS_BATCH_SIZE
138
+ send_buffered_events
139
+ end
140
+ end
141
+
142
+ def send_buffered_events
143
+ msg = {
144
+ :appName => @app_name,
145
+ :customerId => @customer_id,
146
+ :environment => {
147
+ :agentType => RSPEC_FRAMEWORK_NAME,
148
+ :environmentName => @test_stage,
149
+ :testStage => @test_stage,
150
+ :labId => @build_session_id
151
+ },
152
+ :testSelectionStatus => @test_selection_status,
153
+ :events => @buffered_events
154
+ }
155
+
156
+ unless @branch.nil?
157
+ msg[:branch] = @branch
158
+ end
159
+ unless @build.nil?
160
+ msg[:build] = @build
161
+ end
162
+
163
+ url = @server + EVENTS_ENDPOINT
164
+ begin
165
+ msg_json = msg.to_json
166
+ info "Sending #{@buffered_events.size} buffered events"
167
+ @http_client.post url, msg_json
168
+ clear_buffer
169
+ rescue Exception => e
170
+ error "Failed sending data '#{msg}' to '#{url}'", e
171
+ end
172
+ end
173
+
174
+ def clear_buffer
175
+ @buffered_events = []
176
+ end
177
+
178
+ def create_basic_test_event(type, execution_id)
179
+ {
180
+ :type => type,
181
+ :testFramework => RSPEC_FRAMEWORK_NAME,
182
+ :executionId => execution_id,
183
+ :timestamp => current_millis
184
+ }
185
+ end
186
+
187
+ def create_test_event(type, example_group, test_name, execution_id)
188
+ evt = create_basic_test_event(type, execution_id)
189
+ evt[:testName] = replace_reserved_characters(test_name)
190
+ evt[:suitePath] = example_group
191
+ evt
192
+ end
193
+
194
+ def create_test_end_event(example_group, test_name, execution_id, result, duration)
195
+ evt = create_test_event(:testEnd, example_group, test_name, execution_id)
196
+ evt[:result] = result
197
+ evt[:duration] = duration
198
+ evt
199
+ end
200
+
201
+ def current_millis
202
+ (Time.now.to_f * MILLIS_IN_SECOND).to_i
203
+ end
204
+ end
205
+
206
+ class TestIdTracker
207
+ include Singleton
208
+
209
+ def initialize
210
+ @current_test_identifier = nil
211
+ end
212
+
213
+ def to_s
214
+ unless @current_test_identifier.nil?
215
+ return "State:" + @current_test_identifier
216
+ end
217
+ "Not in test"
218
+ end
219
+
220
+ def get_current_test_identifier
221
+ @current_test_identifier
222
+ end
223
+
224
+ def set_current_test_identifier(test_id)
225
+ if @current_test_identifier != test_id
226
+ @current_test_identifier = test_id
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,27 @@
1
+ require_relative './utils'
2
+
3
+ require 'rest-client'
4
+
5
+ #
6
+ # This simple RestClient wrapper automatically adds the necessary headers to HTTP requests. It is required to
7
+ # initialize the :jwt field for RestClientWrapper to work.
8
+ #
9
+ class RestClientWrapper
10
+ attr_writer :jwt
11
+
12
+ def get(url)
13
+ RestClient.get URI::encode(url), :Authorization => auth_header_value(@jwt)
14
+ end
15
+
16
+ def post(url, msg_json)
17
+ info "Sending request: POST #{url} #{msg_json}"
18
+ RestClient.post URI::encode(url), msg_json, :content_type => :json, :accept => :json, :Authorization => auth_header_value(@jwt)
19
+ end
20
+
21
+ private
22
+
23
+ def auth_header_value(jwt)
24
+ "Bearer #{jwt}"
25
+ end
26
+
27
+ end
@@ -0,0 +1,30 @@
1
+ #
2
+ # This script is used for:
3
+ # - subscribing the SeaLights Agent to RSpec test events
4
+ # - apply TIA configuration to perform test selections.
5
+ #
6
+ # This script has to be run before the RSpec test script to take effect.
7
+ #
8
+
9
+ require_relative './config'
10
+ require_relative './listener'
11
+ require_relative './rest-client-wrapper'
12
+ require_relative './tia'
13
+ require_relative './utils'
14
+
15
+ require 'rspec'
16
+
17
+ RSpec.configure do |rspec_config|
18
+ http_client = RestClientWrapper.new
19
+
20
+ cfg = AgentConfiguration.new(http_client, EnvironmentVariablesReader.new)
21
+
22
+ tia_config = TiaConfig.new(cfg.tia_enabled, cfg.server, cfg.build_session_id, cfg.test_stage, cfg.polling_interval_secs, cfg.polling_timeout_secs)
23
+ tia_applier = TiaApplier.new(http_client, RspecExampleClassModifier.new, tia_config)
24
+ tia_applier.apply_configuration
25
+
26
+ listener = RSpecListener.new(http_client, cfg.server, cfg.customer_id, cfg.app_name, cfg.build, cfg.branch, cfg.test_stage, cfg.build_session_id, tia_applier.test_selection_status)
27
+ rspec_config.reporter.register_listener listener, :start, :example_started, :example_passed, :example_failed, :example_pending, :example_finished, :stop
28
+ rescue Exception => e
29
+ error "Could not subscribe RSpec Agent to test run. #{e.message}.", e
30
+ end
data/agent/tia.rb ADDED
@@ -0,0 +1,154 @@
1
+ #
2
+ # This script contains code for obtaining and applying TIA recommendations.
3
+ #
4
+
5
+ require_relative './utils'
6
+
7
+ require 'json'
8
+ require 'rspec'
9
+
10
+ TEST_SELECTION_DISABLED = 'disabled'
11
+ TEST_SELECTION_DISABLED_BY_CONFIG = 'disabledByConfiguration'
12
+ TEST_SELECTION_RECOMMENDATION_TIMEOUT = 'recommendationsTimeout'
13
+ TEST_SELECTION_ERROR = 'error'
14
+ TEST_SELECTION_RECOMMENDED_TEST = 'recommendedTests'
15
+
16
+ class TiaConfig
17
+ attr_reader :tia_enabled, :server, :build_session_id, :test_stage, :polling_interval_secs, :polling_timeout_secs
18
+
19
+ def initialize(tia_enabled, server, build_session_id, test_stage, polling_interval_secs, polling_timeout_secs)
20
+ @tia_enabled = tia_enabled
21
+ @server = server
22
+ @build_session_id = build_session_id
23
+ @test_stage = test_stage
24
+ @polling_interval_secs = polling_interval_secs
25
+ @polling_timeout_secs = polling_timeout_secs
26
+ end
27
+ end
28
+
29
+ #
30
+ # TiaApplier uses the TIA config to get the list of excluded tests and then calls the RSpec modifier to apply test
31
+ # selection.
32
+ #
33
+ class TiaApplier
34
+ attr_reader :test_selection_status
35
+
36
+ def initialize(http_client, rspec_modifier, cfg)
37
+ @http_client = http_client
38
+ @cfg = cfg
39
+ @rspec_modifier = rspec_modifier
40
+
41
+ info "TIA applier initialized with configuration: #{pretty_fields(@cfg)}."
42
+
43
+ # We're starting with the status indicating that TIA recommendations were acquired successfully.
44
+ # The status gets changed, if something goes "wrong" on the way.
45
+ @test_selection_status = TEST_SELECTION_RECOMMENDED_TEST
46
+ end
47
+
48
+ def apply_configuration
49
+ unless @cfg.tia_enabled
50
+ @test_selection_status = TEST_SELECTION_DISABLED_BY_CONFIG
51
+ return
52
+ end
53
+
54
+ excluded_tests = get_excluded_tests
55
+ @rspec_modifier.add_test_exclusion(excluded_tests)
56
+
57
+ info "Applied TIA with excluded tests: #{excluded_tests}"
58
+ rescue Exception => e
59
+ error "An error occurred while applying TIA configuration. Test selection will be skipped.", e
60
+ end
61
+
62
+ private
63
+
64
+ TEST_RECOMMENDATIONS_ENDPOINT = "/v1/test-recommendations/"
65
+ URL_SEPARATOR = "/"
66
+ HTTP_OK = 200
67
+ TEST_SELECTION_ENABLED_FIELD = 'testSelectionEnabled'
68
+ EXCLUDED_TESTS_FIELD = 'excludedTests'
69
+ RECOMMENDATION_SET_STATUS_FIELD = 'recommendationSetStatus'
70
+ NOT_READY_RECOMMENDATION_STATUS = 'notReady'
71
+
72
+ def get_excluded_tests
73
+ url = @cfg.server + TEST_RECOMMENDATIONS_ENDPOINT + @cfg.build_session_id + URL_SEPARATOR + @cfg.test_stage
74
+ recommendations = poll_for_recommendations(url)
75
+
76
+ selection_enabled = recommendations[TEST_SELECTION_ENABLED_FIELD]
77
+ unless selection_enabled
78
+ unless selection_enabled.nil?
79
+ @test_selection_status = TEST_SELECTION_DISABLED
80
+ end
81
+ return []
82
+ end
83
+
84
+ recommendations.fetch(EXCLUDED_TESTS_FIELD, []).map { |excluded_test| excluded_test['name'] }
85
+ rescue Exception => e
86
+ @test_selection_status = TEST_SELECTION_ERROR
87
+ error "Error while getting excluded tests: '#{e.message}'. Proceeding without excluded tests.", e
88
+ end
89
+
90
+ def poll_for_recommendations(url)
91
+ total_wait = 0
92
+ while total_wait < @cfg.polling_timeout_secs
93
+ recommendations = fetch_recommendations(url)
94
+ unless should_keep_polling(recommendations)
95
+ return recommendations
96
+ end
97
+ sleep(@cfg.polling_interval_secs)
98
+ total_wait += @cfg.polling_interval_secs
99
+ end
100
+
101
+ @test_selection_status = TEST_SELECTION_RECOMMENDATION_TIMEOUT
102
+ {}
103
+ end
104
+
105
+ def should_keep_polling(recommendations)
106
+ !recommendations.empty? &&
107
+ recommendations[TEST_SELECTION_ENABLED_FIELD] &&
108
+ recommendations[RECOMMENDATION_SET_STATUS_FIELD] == NOT_READY_RECOMMENDATION_STATUS &&
109
+ @cfg.polling_interval_secs > 0 && @cfg.polling_timeout_secs > 0
110
+ end
111
+
112
+ def fetch_recommendations(url)
113
+ http_response = @http_client.get url
114
+ if http_response.code != HTTP_OK
115
+ @test_selection_status = TEST_SELECTION_ERROR
116
+ return {}
117
+ end
118
+
119
+ JSON.parse(http_response)
120
+ end
121
+ end
122
+
123
+ #
124
+ # This class modifies the `RSpec::Core::Example.run` method to skip the currently run test based on checking if
125
+ # the test's name is in the `excluded_tests` list.
126
+ #
127
+ class RspecExampleClassModifier
128
+ def add_test_exclusion(excluded_tests)
129
+ if excluded_tests.empty?
130
+ return
131
+ end
132
+
133
+ mod = Module.new do
134
+ define_method(:run) do |*args, &blk|
135
+ begin
136
+ test_name = self.metadata[:full_description]
137
+ if excluded_tests.include? replace_reserved_characters(test_name)
138
+ info "Test '#{test_name}' excluded by TIA."
139
+
140
+ def skipped?
141
+ return true
142
+ end
143
+ end
144
+ rescue Exception => e
145
+ error "Unsuccessful TIA test exclusion check. #{e.message}", e
146
+ end
147
+
148
+ super(*args, &blk)
149
+ end
150
+ end
151
+
152
+ RSpec::Core::Example.prepend(mod)
153
+ end
154
+ end
data/agent/utils.rb ADDED
@@ -0,0 +1,51 @@
1
+ DOT = '.'
2
+ DOT_REPLACEMENT = '_'
3
+ SLASH = '/'
4
+ SLASH_REPLACEMENT = '#'
5
+
6
+ #
7
+ # The '.' and '/' are treated by the back-end as separators of package/class/method names. To avoid unexpected
8
+ # processing of RSpec test names, these characters are replaced.
9
+ #
10
+ def replace_reserved_characters(test_name)
11
+ test_name.gsub(DOT, DOT_REPLACEMENT).gsub(SLASH, SLASH_REPLACEMENT)
12
+ end
13
+
14
+ def info(message)
15
+ log(message, :INFO)
16
+ end
17
+
18
+ def error(message, e = nil)
19
+ log(message + (e.nil? ? '' : pretty_backtrace(e)), :ERROR)
20
+ end
21
+
22
+ private
23
+
24
+ def log(message, level)
25
+ puts "[SEALIGHTS] #{Time.now.strftime("%F %T.%3N")} #{level} #{message}"
26
+ end
27
+
28
+ #
29
+ # This method creates a mapping of the field names (without the '@' sign) to their values.
30
+ # An optional list of excluded fields can be passed.
31
+ # For any field called `jwt`only the last six characters of its value are taken. It is assumed that such a field will
32
+ # always hold a JSON Web Token.
33
+ #
34
+ def pretty_fields(object, *excluded_fields)
35
+ fields_values = {}
36
+ object.instance_variables.each do |var|
37
+ field_name = var.to_s.delete('@')
38
+ field_value = object.instance_variable_get(var)
39
+ unless excluded_fields.include? field_name
40
+ fields_values[field_name] = field_name == 'jwt' ? "...#{field_value[-6..-1]}" : field_value
41
+ end
42
+ end
43
+
44
+ fields_values
45
+ end
46
+
47
+ def pretty_backtrace(exception)
48
+ initial_whitespace = "\n\t"
49
+ backtrace_lines = exception.backtrace.map { |s| "#{initial_whitespace}#{s}" }.join
50
+ "#{initial_whitespace}Backtrace:#{backtrace_lines}"
51
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sealights-rspec-agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.2
5
+ platform: ruby
6
+ authors:
7
+ - SeaLights
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - agent/config.rb
20
+ - agent/listener.rb
21
+ - agent/rest-client-wrapper.rb
22
+ - agent/sealights-rspec-agent.rb
23
+ - agent/tia.rb
24
+ - agent/utils.rb
25
+ homepage:
26
+ licenses: []
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - agent
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubygems_version: 3.0.3
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: SeaLights RSpec Agent
47
+ test_files: []