rsmp-validator 0.1.0 → 0.3.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/config/cross_rs4s.yaml +6 -0
  3. data/config/gem_supervisor.yaml +6 -0
  4. data/config/gem_tlc.yaml +6 -0
  5. data/config/kapsch_etx.yaml +13 -6
  6. data/config/lightmotion_satellite.yaml +8 -2
  7. data/config/semaforica_cartesio.yaml +8 -2
  8. data/config/sus.rb +1 -1
  9. data/config/swarco_itc3.yaml +8 -2
  10. data/config/tecsen_tmacs_supervisor.yaml +6 -0
  11. data/exe/rsmp-validator +2 -118
  12. data/lib/rsmp/validator/async_context.rb +4 -3
  13. data/lib/rsmp/validator/auto_node.rb +5 -15
  14. data/lib/rsmp/validator/cli/config_cli.rb +32 -0
  15. data/lib/rsmp/validator/cli/entrypoint.rb +40 -0
  16. data/lib/rsmp/validator/cli/run_options.rb +87 -0
  17. data/lib/rsmp/validator/cli/runner.rb +119 -0
  18. data/lib/rsmp/validator/cli/tee_io.rb +30 -0
  19. data/lib/rsmp/validator/cli/validator_overrides.rb +28 -0
  20. data/lib/rsmp/validator/compliance/config_metadata.rb +31 -0
  21. data/lib/rsmp/validator/compliance/config_sxls.rb +24 -0
  22. data/lib/rsmp/validator/compliance/report.rb +70 -0
  23. data/lib/rsmp/validator/compliance/report_failure.rb +45 -0
  24. data/lib/rsmp/validator/compliance/report_metadata.rb +101 -0
  25. data/lib/rsmp/validator/config_check.rb +72 -0
  26. data/lib/rsmp/validator/configuration/loader.rb +6 -0
  27. data/lib/rsmp/validator/configuration/sxls_override.rb +33 -0
  28. data/lib/rsmp/validator/configuration/validation.rb +1 -35
  29. data/lib/rsmp/validator/configuration/version_normalization.rb +56 -0
  30. data/lib/rsmp/validator/configuration.rb +25 -22
  31. data/lib/rsmp/validator/helpers/alarms.rb +1 -1
  32. data/lib/rsmp/validator/helpers/clock.rb +2 -2
  33. data/lib/rsmp/validator/helpers/connection.rb +18 -8
  34. data/lib/rsmp/validator/helpers/handshake.rb +5 -8
  35. data/lib/rsmp/validator/helpers/input.rb +3 -3
  36. data/lib/rsmp/validator/helpers/security.rb +1 -1
  37. data/lib/rsmp/validator/helpers/signal_plans.rb +5 -5
  38. data/lib/rsmp/validator/helpers/signal_priority.rb +8 -11
  39. data/lib/rsmp/validator/helpers/startup.rb +6 -20
  40. data/lib/rsmp/validator/helpers/status.rb +1 -1
  41. data/lib/rsmp/validator/lifecycle.rb +11 -4
  42. data/lib/rsmp/validator/options/site_test_options.rb +21 -1
  43. data/lib/rsmp/validator/options/supervisor_test_options.rb +50 -13
  44. data/lib/rsmp/validator/site_tester.rb +15 -6
  45. data/lib/rsmp/validator/supervisor_tester.rb +11 -5
  46. data/lib/rsmp/validator/tester.rb +25 -29
  47. data/lib/rsmp/validator/version.rb +1 -1
  48. data/lib/rsmp/validator/version_filter.rb +1 -1
  49. data/lib/rsmp/validator.rb +5 -1
  50. data/schemas/site_test.json +0 -6
  51. data/test/site/core/aggregated_status_spec.rb +5 -7
  52. data/test/site/core/connect_spec.rb +5 -5
  53. data/test/site/core/disconnect_spec.rb +10 -19
  54. data/test/site/core/message_buffer_spec.rb +114 -0
  55. data/test/site/tlc/alarm_spec.rb +7 -7
  56. data/test/site/tlc/clock_spec.rb +26 -26
  57. data/test/site/tlc/detector_logics_spec.rb +6 -6
  58. data/test/site/tlc/emergency_routes_spec.rb +16 -16
  59. data/test/site/tlc/input_spec.rb +10 -10
  60. data/test/site/tlc/invalid_command_spec.rb +24 -20
  61. data/test/site/tlc/invalid_status_spec.rb +25 -20
  62. data/test/site/tlc/modes_spec.rb +53 -37
  63. data/test/site/tlc/output_spec.rb +8 -8
  64. data/test/site/tlc/signal_groups_spec.rb +8 -8
  65. data/test/site/tlc/signal_plans_spec.rb +32 -34
  66. data/test/site/tlc/signal_priority_spec.rb +10 -12
  67. data/test/site/tlc/subscribe_spec.rb +9 -9
  68. data/test/site/tlc/system_spec.rb +12 -10
  69. data/test/site/tlc/traffic_data_spec.rb +27 -16
  70. data/test/site/tlc/traffic_situations_spec.rb +5 -5
  71. data/test/supervisor/connect_spec.rb +9 -12
  72. metadata +35 -6
@@ -4,11 +4,11 @@ module RSMP
4
4
  # Helpers for connecting to a site or supervisor in tests, with optional
5
5
  # version-based skipping via sxl: and core: keyword arguments.
6
6
  #
7
- # with_site(:connected, sxl: '>=1.2') do |supervisor, site_proxy|
7
+ # with_site(:connected, sxl: '>=1.2.0') do |supervisor, site_proxy|
8
8
  # ...
9
9
  # end
10
10
  #
11
- # with_supervisor(:connected, core: '>=3.2') do |site, supervisor_proxy|
11
+ # with_supervisor(:connected, core: '>=3.2.0') do |site, supervisor_proxy|
12
12
  # ...
13
13
  # end
14
14
  module Connection
@@ -31,10 +31,10 @@ module RSMP
31
31
  else
32
32
  RSMP::Validator::SiteTester.public_send(state, **opts) do |_task, _node, proxy|
33
33
  block.call(proxy)
34
- rescue RSMP::TimeoutError => e
35
- @__assertions__.assert false, e.message
34
+ rescue RSMP::OperationError => e
35
+ fail_test e.failure.message
36
36
  rescue StandardError => e
37
- @__assertions__.error!(UncaughtException.new(e))
37
+ error_test UncaughtException.new(e)
38
38
  end
39
39
  end
40
40
  end
@@ -47,16 +47,26 @@ module RSMP
47
47
  else
48
48
  RSMP::Validator::SupervisorTester.public_send(state, **opts) do |_task, _node, proxy|
49
49
  block.call(proxy)
50
- rescue RSMP::TimeoutError => e
51
- @__assertions__.assert false, e.message
50
+ rescue RSMP::OperationError => e
51
+ fail_test e.failure.message
52
52
  rescue StandardError => e
53
- @__assertions__.error!(UncaughtException.new(e))
53
+ error_test UncaughtException.new(e)
54
54
  end
55
55
  end
56
56
  end
57
57
 
58
58
  private
59
59
 
60
+ def fail_test(message)
61
+ @__assertions__.assert false, message
62
+ throw :rsmp_validator_test_failure
63
+ end
64
+
65
+ def error_test(error)
66
+ @__assertions__.error! error
67
+ throw :rsmp_validator_test_failure
68
+ end
69
+
60
70
  def validate_state!(state)
61
71
  return if VALID_STATES.include?(state)
62
72
 
@@ -28,17 +28,14 @@ module RSMP
28
28
 
29
29
  RSMP::Validator::SiteTester.isolated(
30
30
  'collect' => { timeout: timeout, num: length, ingoing: true, outgoing: true },
31
- 'sites' => { 'default' => { 'rsmp_versions' => [core_version] } }
32
- ) do |task, _supervisor, site|
31
+ 'sites' => { 'default' => { 'core_version' => core_version } }
32
+ ) do |_task, _supervisor, site|
33
33
  assert(site.ready?, 'expected site to be ready')
34
34
  collector = site.collector
35
- collector.use_task task
36
- collector.wait!
37
- got = collector.messages.map { |message| "#{message.direction}:#{message.type}" }
35
+ messages = collector.wait!
36
+ got = messages.map { |message| "#{message.direction}:#{message.type}" }
38
37
  end
39
38
  got
40
- rescue Async::TimeoutError
41
- raise "Did not collect #{length} messages within #{timeout}s"
42
39
  end
43
40
 
44
41
  def check_sequence_v311_to_v313(core_version)
@@ -96,7 +93,7 @@ module RSMP
96
93
  case version
97
94
  when '3.1.1', '3.1.2', '3.1.3'
98
95
  check_sequence_v311_to_v313 version
99
- when '3.1.4', '3.1.5', '3.2', '3.2.1', '3.2.2'
96
+ when '3.1.4', '3.1.5', '3.2.0', '3.2.1', '3.2.2'
100
97
  check_sequence_v314_or_later version
101
98
  when '3.3.0'
102
99
  check_sequence_v330 version
@@ -6,7 +6,7 @@ module RSMP
6
6
  include Status
7
7
 
8
8
  def force_input_and_confirm(site_proxy, input:, value:, within:)
9
- site_proxy.tlc.force_input(input:, status: 'True', value:, within:)
9
+ site_proxy.tlc.force_input!(input:, status: 'True', value:, within:)
10
10
  digit = (value == 'True' ? '1' : '0')
11
11
 
12
12
  wait_for_status(
@@ -19,7 +19,7 @@ module RSMP
19
19
  end
20
20
 
21
21
  def switch_input(site_proxy, indx, within:)
22
- site_proxy.tlc.set_input(input: indx.to_s, status: 'True', within:)
22
+ site_proxy.tlc.set_input!(input: indx.to_s, status: 'True', within:)
23
23
 
24
24
  wait_for_status(
25
25
  site_proxy,
@@ -29,7 +29,7 @@ module RSMP
29
29
  ]
30
30
  )
31
31
 
32
- site_proxy.tlc.set_input(input: indx.to_s, status: 'False', within:)
32
+ site_proxy.tlc.set_input!(input: indx.to_s, status: 'False', within:)
33
33
  wait_for_status(
34
34
  site_proxy,
35
35
  "input #{indx} to be False",
@@ -12,7 +12,7 @@ module RSMP
12
12
  component = RSMP::Validator.get_config('components', 'detector_logic').keys[0]
13
13
  timeout = RSMP::Validator.get_config('timeouts', 'command_response')
14
14
  site_proxy.send_command_and_collect(command_list, component: component,
15
- within: timeout).ok!
15
+ within: timeout)
16
16
  end
17
17
 
18
18
  def require_security_codes
@@ -5,30 +5,30 @@ module RSMP
5
5
  module SignalPlans
6
6
  def with_cycle_time_extended(site_proxy, extension = 5, &block)
7
7
  timeout = RSMP::Validator.get_config('timeouts', 'command_response')
8
- plan = site_proxy.tlc.read_current_plan
8
+ plan = site_proxy.tlc.read_current_plan!
9
9
  time = read_plan_cycle_time(site_proxy, plan)
10
10
  need_to_reset = true
11
11
  time_extended = time + extension
12
- site_proxy.tlc.set_cycle_time(plan: plan, cycle_time: time_extended, within: timeout)
12
+ site_proxy.tlc.set_cycle_time!(plan: plan, cycle_time: time_extended, within: timeout)
13
13
  verify_cycle_time(site_proxy, plan, time_extended)
14
14
  block.yield
15
15
  ensure
16
16
  if need_to_reset
17
17
  log 'Reset cycle time'
18
- site_proxy.tlc.set_cycle_time(plan: plan, cycle_time: time, within: timeout)
18
+ site_proxy.tlc.set_cycle_time!(plan: plan, cycle_time: time, within: timeout)
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
23
 
24
24
  def read_plan_cycle_time(site_proxy, plan)
25
- time = site_proxy.tlc.read_cycle_times[plan]
25
+ time = site_proxy.tlc.read_cycle_times![plan]
26
26
  assert(!time.nil?, 'Site returned empty cycle times list')
27
27
  time
28
28
  end
29
29
 
30
30
  def verify_cycle_time(site_proxy, plan, expected)
31
- actual = site_proxy.tlc.read_cycle_times[plan]
31
+ actual = site_proxy.tlc.read_cycle_times![plan]
32
32
  assert(actual == expected, "Expected cycle time #{expected}, got #{actual}")
33
33
  end
34
34
  end
@@ -40,15 +40,14 @@ module RSMP
40
40
  class RequestHelper < RSMP::Queue
41
41
  include RSMP::Validator::Helpers::Status
42
42
 
43
- def initialize(site_proxy, component:, signal_group_id:, timeout:, task:)
43
+ def initialize(site_proxy, component:, signal_group_id:, timeout:)
44
44
  super(site_proxy,
45
45
  filter: RSMP::Filter.new(
46
46
  type: 'StatusUpdate',
47
47
  ingoing: true,
48
48
  outgoing: false,
49
49
  component: component
50
- ),
51
- task: task)
50
+ ))
52
51
  @site_proxy = site_proxy
53
52
  @component = component
54
53
  @signal_group_id = signal_group_id
@@ -76,7 +75,7 @@ module RSMP
76
75
  'level' => level,
77
76
  'eta' => eta,
78
77
  'vehicleType' => vehicle_type).to_a
79
- @site_proxy.send_command(command_list, component: @component)
78
+ @site_proxy.send_command!(command_list, component: @component)
80
79
  end
81
80
 
82
81
  def request_unrelated(level: 7, eta: 2, vehicle_type: 'car')
@@ -87,21 +86,19 @@ module RSMP
87
86
  'level' => level,
88
87
  'eta' => eta,
89
88
  'vehicleType' => vehicle_type).to_a
90
- @site_proxy.send_command(command_list, component: @component)
89
+ @site_proxy.send_command!(command_list, component: @component)
91
90
  end
92
91
 
93
92
  def cancel
94
93
  command_list = RSMP::CommandList.new(:M0022, :requestPriority,
95
94
  requestId: @request_id,
96
95
  type: 'cancel').to_a
97
- @site_proxy.send_command(command_list, component: @component)
96
+ @site_proxy.send_command!(command_list, component: @component)
98
97
  end
99
98
 
100
99
  def expect(state)
101
100
  @matcher.state = state
102
- wait_for_message timeout: @timeout
103
- rescue RSMP::TimeoutError
104
- raise RSMP::TimeoutError, "Priority request did not reach state #{state} within #{@timeout}s"
101
+ wait_for_message!(timeout: @timeout)
105
102
  end
106
103
 
107
104
  private
@@ -112,11 +109,11 @@ module RSMP
112
109
 
113
110
  def start
114
111
  start_receiving
115
- @site_proxy.subscribe_to_status @subscribe_list, component: @component
112
+ @site_proxy.subscribe_to_status! @subscribe_list, component: @component
116
113
  end
117
114
 
118
115
  def stop
119
- @site_proxy.unsubscribe_to_status @unsubscribe_list, component: @component
116
+ @site_proxy.unsubscribe_to_status! @unsubscribe_list, component: @component
120
117
  stop_receiving
121
118
  end
122
119
 
@@ -83,7 +83,7 @@ module RSMP
83
83
  end
84
84
 
85
85
  def wait_normal_control(site_proxy, timeout: RSMP::Validator.get_config('timeouts', 'startup_sequence'))
86
- site_proxy.tlc.wait_for_normal_control(timeout: timeout)
86
+ site_proxy.tlc.wait_for_normal_control!(timeout: timeout)
87
87
  end
88
88
 
89
89
  def verify_startup_sequence(site_proxy)
@@ -94,13 +94,14 @@ module RSMP
94
94
  collector = RSMP::StatusCollector.new site_proxy, status_list, timeout: timeout
95
95
  sequencer = SignalGroupSequence.new RSMP::Validator.get_config('startup_sequence')
96
96
  collector_task = start_sequence_collector(collector, sequencer)
97
- site_proxy.subscribe_to_status subscribe_list, component: component
97
+ site_proxy.subscribe_to_status! subscribe_list, component: component
98
98
  yield
99
- handle_startup_sequence_result(collector_task.wait, sequencer, collector, timeout)
99
+ collector_task.wait.value!
100
+ log 'Startup sequence verified'
100
101
  wait_for_status(site_proxy, 'control mode to be startup',
101
- [{ 'sCI' => 'S0020', 'n' => 'controlmode', 's' => 'control' }])
102
+ [{ 'sCI' => 'S0020', 'n' => 'controlmode', 's' => ['control'] }])
102
103
  ensure
103
- site_proxy.unsubscribe_to_status unsubscribe_list, component: component
104
+ site_proxy.unsubscribe_to_status! unsubscribe_list, component: component
104
105
  end
105
106
 
106
107
  private
@@ -136,21 +137,6 @@ module RSMP
136
137
  log "Startup sequence #{states}: Fail"
137
138
  collector.cancel status
138
139
  end
139
-
140
- def handle_startup_sequence_result(result, sequencer, collector, timeout)
141
- case result
142
- when :ok
143
- log 'Startup sequence verified'
144
- when :timeout
145
- raise(
146
- "Startup sequence '#{sequencer.sequence}' didn't complete in #{timeout}s, " \
147
- "reached #{sequencer.latest}, #{sequencer.num_started} started, " \
148
- "#{sequencer.num_done} done"
149
- )
150
- when :cancelled
151
- raise "Startup sequence '#{sequencer.sequence}' not followed: #{collector.error}"
152
- end
153
- end
154
140
  end
155
141
  end
156
142
  end
@@ -8,7 +8,7 @@ module RSMP
8
8
  timeout = options.fetch(:timeout, RSMP::Validator.get_config('timeouts', 'command'))
9
9
  component_id = options.fetch(:component_id, RSMP::Validator.get_config('main_component'))
10
10
  log "Wait for #{description}"
11
- site_proxy.tlc.wait_for_status(
11
+ site_proxy.tlc.wait_for_status!(
12
12
  description,
13
13
  RSMP::StatusList.new(status_list).to_a,
14
14
  update_rate: update_rate,
@@ -51,17 +51,20 @@ module RSMP
51
51
 
52
52
  # Called at sus shutdown: stops the auto node and reactor.
53
53
  def after_suite
54
- reactor.run do |_task|
55
- auto_node&.stop
54
+ task = reactor.run do |_task|
55
+ begin
56
+ auto_node&.stop
57
+ ensure
58
+ current_tester&.stop
59
+ end
56
60
  ensure
57
61
  reactor.interrupt
58
62
  end
63
+ task.wait
59
64
  # Explicitly close the reactor now, while the log stream is still open.
60
65
  # Without this, Ruby's fiber scheduler hook fires after the File.open block
61
66
  # has closed the log file, causing IOError when cancelled tasks try to log.
62
67
  reactor.close
63
- rescue StandardError
64
- nil
65
68
  end
66
69
 
67
70
  # Initialize the Async reactor.
@@ -72,6 +75,10 @@ module RSMP
72
75
 
73
76
  private
74
77
 
78
+ def current_tester
79
+ mode == :site ? SiteTester.instance : SupervisorTester.instance
80
+ end
81
+
75
82
  def load_log_defaults(name)
76
83
  path = File.expand_path("../../../config/#{name}.yaml", __dir__)
77
84
  YAML.load_file(path)
@@ -35,17 +35,37 @@ module RSMP
35
35
  value = options[key]
36
36
  next if value.nil? || merged_default.key?(key)
37
37
 
38
+ value = rsmp_timeouts(value) if key == 'timeouts'
39
+ next if value == {}
40
+
38
41
  merged_default[key] = value
39
42
  end
40
43
 
41
44
  merged_default
42
45
  end
43
46
 
47
+ def rsmp_timeouts(value)
48
+ return value unless value.is_a?(Hash)
49
+
50
+ value.slice(*rsmp_timeout_keys)
51
+ end
52
+
53
+ def rsmp_timeout_keys
54
+ %w[
55
+ connect
56
+ ready
57
+ watchdog
58
+ acknowledgement
59
+ command
60
+ command_timeout
61
+ status_response
62
+ ]
63
+ end
64
+
44
65
  def site_defaults
45
66
  %w[
46
67
  sxls
47
68
  core_version
48
- rsmp_versions
49
69
  intervals
50
70
  timeouts
51
71
  components
@@ -21,29 +21,66 @@ module RSMP
21
21
  local_site = options['local_site']
22
22
  return options unless local_site.is_a?(Hash)
23
23
 
24
+ merge_site_defaults(options, local_site)
25
+ site_options_class = site_options_class_for(local_site)
26
+ local_site = normalize_local_site_for_rsmp(local_site)
27
+ local_site = site_options_class.new(local_site).to_h
28
+
29
+ options.merge('local_site' => local_site)
30
+ end
31
+
32
+ def merge_site_defaults(options, local_site)
33
+ rsmp_site_default_keys.each do |key|
34
+ value = options[key]
35
+ next if value.nil? || local_site.key?(key)
36
+
37
+ value = rsmp_timeouts(value) if key == 'timeouts'
38
+ next if value == {}
39
+
40
+ local_site[key] = value
41
+ end
42
+ end
43
+
44
+ def rsmp_site_default_keys
24
45
  %w[
25
46
  sxls
26
47
  core_version
27
- rsmp_versions
28
48
  intervals
29
49
  timeouts
30
50
  components
31
51
  skip_validation
32
- ].each do |key|
33
- value = options[key]
34
- next if value.nil? || local_site.key?(key)
52
+ ]
53
+ end
35
54
 
36
- local_site[key] = value
37
- end
55
+ def site_options_class_for(local_site)
56
+ local_site['type'] == 'tlc' ? RSMP::TLC::TrafficControllerSite::Options : RSMP::Site::Options
57
+ end
38
58
 
39
- site_options_class = if local_site['type'] == 'tlc'
40
- RSMP::TLC::TrafficControllerSite::Options
41
- else
42
- RSMP::Site::Options
43
- end
44
- local_site = site_options_class.new(local_site).to_h
59
+ def rsmp_timeouts(value)
60
+ return value unless value.is_a?(Hash)
45
61
 
46
- options.merge('local_site' => local_site)
62
+ value.slice(*rsmp_timeout_keys)
63
+ end
64
+
65
+ def rsmp_timeout_keys
66
+ %w[
67
+ connect
68
+ ready
69
+ watchdog
70
+ acknowledgement
71
+ command
72
+ command_timeout
73
+ status_response
74
+ ]
75
+ end
76
+
77
+ def normalize_local_site_for_rsmp(local_site)
78
+ normalized = local_site.dup
79
+ if normalized['security_codes'].nil? && normalized.dig('secrets', 'security_codes').is_a?(Hash)
80
+ normalized['security_codes'] = normalized['secrets']['security_codes']
81
+ end
82
+ normalized.delete('secrets')
83
+ normalized
47
84
  end
48
85
  end
49
86
  end
@@ -68,7 +68,9 @@ module RSMP
68
68
 
69
69
  def build_node(options)
70
70
  logger = create_supervisor_logger(@supervisor_config)
71
- supervisor_settings = ConfigNormalizer.normalize_supervisor_settings(@supervisor_config.deep_merge(options))
71
+ supervisor_settings = ConfigNormalizer.normalize_supervisor_settings(
72
+ @supervisor_config.deep_merge(rsmp_node_options(options))
73
+ )
72
74
 
73
75
  RSMP::Supervisor.new(
74
76
  supervisor_settings: supervisor_settings,
@@ -82,20 +84,23 @@ module RSMP
82
84
  return if @proxy
83
85
 
84
86
  log 'Waiting for site to connect'
85
- @proxy = @node.wait_for_site(:any, timeout: config['timeouts']['connect'])
86
- rescue RSMP::TimeoutError
87
- raise RSMP::ConnectionError, "Site did not connect within #{config['timeouts']['connect']}s"
87
+ result = @node.wait_for_site(:any, timeout: config['timeouts']['connect'])
88
+ raise RSMP::ConnectionError, "Site did not connect within #{config['timeouts']['connect']}s" if result.failure?
89
+
90
+ @proxy = result.value
88
91
  end
89
92
 
90
93
  def wait_for_handshake
91
94
  return if @proxy.ready?
92
95
 
93
96
  log 'Waiting for handshake to complete'
94
- @proxy.wait_for_state :ready, timeout: config['timeouts']['ready']
97
+ result = @proxy.wait_for_state :ready, timeout: config['timeouts']['ready']
98
+ raise RSMP::ConnectionError, result.failure.message if result.failure?
99
+
95
100
  log 'Ready'
96
101
  return if @initial_unsubscribe_done
97
102
 
98
- @proxy.unsubscribe_from_all component: RSMP::Validator.get_config('main_component')
103
+ @proxy.unsubscribe_from_all(component: RSMP::Validator.get_config('main_component')).value!
99
104
  @initial_unsubscribe_done = true
100
105
  end
101
106
 
@@ -108,6 +113,10 @@ module RSMP
108
113
  logger_settings.delete('stream') if log_settings['path']
109
114
  RSMP::Logger.new(logger_settings)
110
115
  end
116
+
117
+ def rsmp_node_options(options)
118
+ options.except('collect')
119
+ end
111
120
  end
112
121
  end
113
122
  end
@@ -39,7 +39,7 @@ module RSMP
39
39
  else
40
40
  RSMP::Site
41
41
  end
42
- site_settings = ConfigNormalizer.normalize_site_settings(@site_config.deep_merge(options))
42
+ site_settings = ConfigNormalizer.normalize_site_settings(@site_config.deep_merge(rsmp_node_options(options)))
43
43
  logger = create_site_logger(@site_config)
44
44
  @site = klass.new(
45
45
  site_settings: site_settings,
@@ -51,14 +51,16 @@ module RSMP
51
51
  def wait_for_connection
52
52
  log 'Waiting for connection to supervisor'
53
53
  @proxy = @node.find_supervisor :any
54
- @proxy.wait_for_state %i[connected ready], timeout: config['timeouts']['connect']
55
- rescue RSMP::TimeoutError
54
+ result = @proxy.wait_for_state %i[connected ready], timeout: config['timeouts']['connect']
55
+ return if result.success?
56
+
56
57
  raise RSMP::ConnectionError, "Could not connect to supervisor within #{config['timeouts']['connect']}s"
57
58
  end
58
59
 
59
60
  def wait_for_handshake
60
- @proxy.wait_for_state :ready, timeout: config['timeouts']['ready']
61
- rescue RSMP::TimeoutError
61
+ result = @proxy.wait_for_state :ready, timeout: config['timeouts']['ready']
62
+ return if result.success?
63
+
62
64
  raise RSMP::ConnectionError, "Handshake didn't complete within #{config['timeouts']['ready']}s"
63
65
  end
64
66
 
@@ -71,6 +73,10 @@ module RSMP
71
73
  logger_settings.delete('stream') if log_settings['path']
72
74
  RSMP::Logger.new(logger_settings)
73
75
  end
76
+
77
+ def rsmp_node_options(options)
78
+ options.except('collect')
79
+ end
74
80
  end
75
81
  end
76
82
  end
@@ -8,10 +8,6 @@ module RSMP
8
8
  class Tester
9
9
  include RSMP::Validator::Log
10
10
 
11
- def self.sentinel_errors
12
- @sentinel_errors ||= []
13
- end
14
-
15
11
  def config
16
12
  RSMP::Validator.config
17
13
  end
@@ -22,7 +18,9 @@ module RSMP
22
18
  def connected(options = {})
23
19
  start options, 'Connecting'
24
20
  wait_for_proxy
25
- yield Async::Task.current, @node, @proxy
21
+ result = yield Async::Task.current, @node, @proxy
22
+ check_health
23
+ result
26
24
  end
27
25
 
28
26
  # Disconnects the site if connected, then waits until the site is connected
@@ -31,7 +29,9 @@ module RSMP
31
29
  stop 'Reconnecting'
32
30
  start options
33
31
  wait_for_proxy
34
- yield Async::Task.current, @node, @proxy
32
+ result = yield Async::Task.current, @node, @proxy
33
+ check_health
34
+ result
35
35
  end
36
36
 
37
37
  # Like connected, except that the connection is closed after the test.
@@ -40,6 +40,7 @@ module RSMP
40
40
  start options, 'Connecting'
41
41
  wait_for_proxy
42
42
  yield Async::Task.current, @node, @proxy
43
+ ensure
43
44
  stop 'Isolating'
44
45
  end
45
46
 
@@ -51,42 +52,37 @@ module RSMP
51
52
 
52
53
  # Stop the rsmp supervisor
53
54
  def stop(why = nil)
54
- if @node
55
- log why if why
56
- @node.ignore_errors RSMP::DisconnectError do
57
- @node.stop
58
- end
59
- end
55
+ log why if why && @node
56
+ @node&.stop
57
+ @node_task&.wait
58
+ @node_task = nil
60
59
  @node = nil
61
60
  @proxy = nil
62
61
  end
63
62
 
63
+ def receive_event(event)
64
+ failure = event.failure
65
+ detail = failure ? "#{failure.code}: #{failure.message}" : event.type
66
+ log "Node event: #{detail}", level: :debug
67
+ end
68
+
64
69
  private
65
70
 
71
+ def check_health
72
+ @node_task.wait if @node_task&.failed?
73
+ end
74
+
66
75
  def initialize
67
76
  parse_config
68
77
  end
69
78
 
70
- # Start the tester node inside an async task that will persist between tests.
79
+ # Start the tester node under the shared reactor's current task.
71
80
  def start(options = {}, _why = nil)
72
81
  return if @node
73
82
 
74
- RSMP::Validator.reactor.async do |task|
75
- task.annotate 'node runner'
76
-
77
- @node = build_node options
78
-
79
- RSMP::Validator.reactor.async do |sentinel|
80
- sentinel.annotate 'sentinel'
81
- while @node
82
- e = @node.error_queue.dequeue
83
- log "Sentinel warning: #{e.class}: #{e}"
84
- self.class.sentinel_errors << e
85
- end
86
- end
87
-
88
- @node.start
89
- end
83
+ @node = build_node options
84
+ @node.add_event_receiver(self)
85
+ @node_task = @node.start(parent: Async::Task.current)
90
86
  end
91
87
 
92
88
  # Wait until communication has been established, and handshake completed.
@@ -1,5 +1,5 @@
1
1
  module RSMP
2
2
  module Validator
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.3.2'.freeze
4
4
  end
5
5
  end
@@ -11,7 +11,7 @@ module RSMP
11
11
  end
12
12
 
13
13
  # Check if the configured core version satisfies the given requirement.
14
- # @param requirement [String] Gem::Requirement-compatible string, e.g. ">= 3.2"
14
+ # @param requirement [String] Gem::Requirement-compatible string, e.g. ">= 3.2.0"
15
15
  def self.core_matches?(requirement)
16
16
  version_satisfies?(requirement, RSMP::Validator.config['core_version'])
17
17
  end