diff --git a/Gemfile.lock b/Gemfile.lock index 9423f1e9..d516ff44 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,10 +11,10 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - async (2.12.0) + async (2.12.1) console (~> 1.25, >= 1.25.2) fiber-annotation - io-event (~> 1.6) + io-event (~> 1.6, >= 1.6.5) async-io (1.43.2) async base64 (0.2.0) @@ -22,7 +22,7 @@ GEM colorize (1.1.0) concurrent-ruby (1.3.3) connection_pool (2.4.1) - console (1.25.2) + console (1.27.0) fiber-annotation fiber-local (~> 1.1) json @@ -31,11 +31,11 @@ GEM fiber-annotation (0.2.0) fiber-local (1.1.0) fiber-storage - fiber-storage (0.1.2) + fiber-storage (1.0.0) hana (1.3.7) i18n (1.14.5) concurrent-ruby (~> 1.0) - io-event (1.6.4) + io-event (1.6.5) json (2.7.2) json_schemer (2.3.0) bigdecimal @@ -46,7 +46,7 @@ GEM mutex_m (0.2.0) rake (13.2.1) regexp_parser (2.9.2) - rsmp (0.29.0) + rsmp (0.31.0) async (~> 2.12.0) async-io (~> 1.43.0) colorize (~> 1.1) diff --git a/config/gem_tlc.yaml b/config/gem_tlc.yaml index f230c771..a3bfb537 100644 --- a/config/gem_tlc.yaml +++ b/config/gem_tlc.yaml @@ -49,3 +49,6 @@ alarms: restrict_testing: sxl_version: 1.2.1 core_version: 3.2.2 +log: + watchdogs: false + acknowledgements: false \ No newline at end of file diff --git a/spec/site/tlc/alarm_spec.rb b/spec/site/tlc/alarm_spec.rb index 6c4d8c6d..fa6ffd12 100644 --- a/spec/site/tlc/alarm_spec.rb +++ b/spec/site/tlc/alarm_spec.rb @@ -75,7 +75,7 @@ def verify_timestamp alarm, duration=1.minute collect_task = task.async do RSMP::AlarmCollector.new(site, num: 1, - query: { + matcher: { 'aCId' => alarm_code_id, 'aSp' => /Acknowledge/i, 'ack' => /Acknowledged/i, diff --git a/spec/site/tlc/clock_spec.rb b/spec/site/tlc/clock_spec.rb index de78060e..ed6c2ad1 100644 --- a/spec/site/tlc/clock_spec.rb +++ b/spec/site/tlc/clock_spec.rb @@ -71,12 +71,12 @@ status = status_list.keys.first.to_s received = Time.new( - collector.query_result( {"sCI" => status, "n" => "year"} )['s'], - collector.query_result( {"sCI" => status, "n" => "month"} )['s'], - collector.query_result( {"sCI" => status, "n" => "day"} )['s'], - collector.query_result( {"sCI" => status, "n" => "hour"} )['s'], - collector.query_result( {"sCI" => status, "n" => "minute"} )['s'], - collector.query_result( {"sCI" => status, "n" => "second"} )['s'], + collector.matcher_result( {"sCI" => status, "n" => "year"} )['s'], + collector.matcher_result( {"sCI" => status, "n" => "month"} )['s'], + collector.matcher_result( {"sCI" => status, "n" => "day"} )['s'], + collector.matcher_result( {"sCI" => status, "n" => "hour"} )['s'], + collector.matcher_result( {"sCI" => status, "n" => "minute"} )['s'], + collector.matcher_result( {"sCI" => status, "n" => "second"} )['s'], 'UTC' ) diff --git a/spec/site/tlc/signal_priority_spec.rb b/spec/site/tlc/signal_priority_spec.rb index fc9d56bd..2f13a427 100644 --- a/spec/site/tlc/signal_priority_spec.rb +++ b/spec/site/tlc/signal_priority_spec.rb @@ -52,92 +52,99 @@ end end - # Validate that signal priority status are send when priorty is requested + # Validate that a signal priority completes when we cancel it. # # 1. Given the site is connected # 2. And we subscribe to signal priority status - # 2. When we send a signal priority request - # 3. Then we should receive status updates - it 'state goes through received, activated, completed', sxl: '>=1.1' do |example| + # 3. When we send a signal priority request + # 4. Then the request state should become 'received' + # 5. Then the request state should become 'activated' + # 6. When we cancel the request + # 7. Then the state should become 'completed' + + it 'becomes completed when cancelled', sxl: '>=1.1' do |example| Validator::Site.connected do |task,supervisor,site| - sequence = ['received','activated','completed'] - # subscribe + timeout = Validator.get_config('timeouts','priority_completion') component = Validator.get_config('main_component') - log "Subscribing to signal priority request status updates" - status_list = [{'sCI'=>'S0033','n'=>'status','uRt'=>'0'}] - status_list.map! { |item| item.merge!('sOc' => true) } if use_sOc?(site) - site.subscribe_to_status component, status_list - - # start collector - request_id = SecureRandom.uuid()[0..3] # make a message id - num = sequence.length - states = [] - result = nil - collector = nil - collect_task = task.async do - collector = RSMP::Collector.new( - site, - type: "StatusUpdate", - num: num, - timeout: Validator.get_config('timeouts','priority_completion'), - component: component - ) - - def search_for_request_state request_id, message, states - message.attribute('sS').each do |status| - return nil unless status['sCI'] == 'S0033' && status['n'] == 'status' - status['s'].each do |priority| - next unless priority['r'] == request_id # is this our request - state = priority['s'] - next unless state != states.last # did the state change? - log "Priority request reached state '#{state}'" - return state - end - end - nil - end - - result = collector.collect do |message| - state = search_for_request_state request_id, message, states - next unless state - states << state - :keep - end - end + signal_group_id = Validator.get_config('components','signal_group').keys.first + prio = Validator::StatusHelpers::SignalPriorityRequestHelper.new( + site, + component: component, + signal_group_id: signal_group_id, + timeout: timeout, + task: task + ) + + prio.run do + log "Before: Send unrelated signal priority request." + prio.request_unrelated + + log "Send signal priority request, wait for reception." + prio.request - def send_priority_request log, id:nil, site:, component: - # send an unrelated request before our request, to check that it does not interfere - log log - signal_group = Validator.get_config('components','signal_group').keys.first - command_list = build_command_list :M0022, :requestPriority, { - requestId: (id || SecureRandom.uuid()[0..3]), - signalGroupId: signal_group, - type: 'new', - level: 7, - eta: 2, - vehicleType: 'car' - } - site.send_command component, command_list + log "After: Send unrelated signal priority request." + prio.request_unrelated + + prio.expect :received + log "Signal priority request was received, wait for activation." + + prio.expect :activated + log "Signal priority request was activated, now cancel it and wait for completion." + + + prio.cancel + prio.expect :completed + log "Signal priority request was completed." end + end + end + + # Validate that a signal priority times out if not cancelled. + # + # 1. Given the site is connected + # 2. And we subscribe to signal priority status + # 3. When we send a signal priority request + # 4. Then the request state should become 'received' + # 5. Then the request state should become 'activated' + # 6. When we do not cancel the request + # 7. Then the state should become 'stale' + + it 'becomes stale if not cancelled', sxl: '>=1.1' do |example| + Validator::Site.connected do |task,supervisor,site| + timeout = Validator.get_config('timeouts','priority_completion') + component = Validator.get_config('main_component') + signal_group_id = Validator.get_config('components','signal_group').keys.first + prio = Validator::StatusHelpers::SignalPriorityRequestHelper.new( + site, + component: component, + signal_group_id: signal_group_id, + timeout: timeout, + task: task + ) + + prio.run do + log "Before: Send unrelated signal priority request." + prio.request_unrelated - send_priority_request "Send an unrelated signal priority request before", - site: site, component: component - send_priority_request "Send our signal priority request", - site: site, component: component, id: request_id - send_priority_request "Send an unrelated signal priority request after", - site: site, component: component - - # wait for collector to complete and check result - collect_task.wait - expect(result).to eq(:ok) - expect(collector.messages).to be_an(Array) - expect(collector.messages.size).to eq(num) - expect(states).to eq(sequence), "Expected state sequence #{sequence}, got #{states}" - ensure - # unsubcribe - unsubscribe_list = status_list.map { |item| item.slice('sCI','n') } - site.unsubscribe_to_status component, unsubscribe_list + log "Send signal priority request, wait for reception." + prio.request + + log "After: Send unrelated signal priority request." + prio.request_unrelated + + prio.expect :received + log "Signal priority request was received, wait for activation." + + prio.expect :activated + log "Signal priority request was activated, wait for it to become stale." + + + # don't cancel request, it should then become stale by itself + prio.expect :stale + log "Signal priority request became stale." + end end end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7904b7cd..d961de5f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,7 +10,8 @@ require_relative 'support/test_supervisor' require_relative 'support/command_helpers' require_relative 'support/status_helpers' -require_relative 'support/sequence_helper' +require_relative 'support/signal_group_sequence_helper' +require_relative 'support/signal_priority_request_helper' require_relative 'support/log_helpers' require_relative 'support/handshake_helper' require_relative 'support/formatters/report_stream.rb' @@ -18,6 +19,7 @@ require_relative 'support/formatters/brief.rb' require_relative 'support/formatters/details.rb' require_relative 'support/formatters/list.rb' +require_relative 'support/formatters/steps.rb' include RSpec include Validator::Log diff --git a/spec/support/command_helpers.rb b/spec/support/command_helpers.rb index 79a452be..d3f50a2d 100644 --- a/spec/support/command_helpers.rb +++ b/spec/support/command_helpers.rb @@ -104,7 +104,7 @@ def set_functional_position status, timeout_minutes:0 securityCode: Validator.get_config('secrets','security_codes',2), status: status, timeout: timeout_minutes, - intersection: 0 + intersection: 0, } send_command_and_confirm @task, command_list, "Switch to functional position #{status}" end @@ -216,7 +216,7 @@ def get_dynamic_bands plan, band timeout: Validator.get_config('timeouts','status_update', default: 0) } collector = result[:collector] - collector.queries.first.got['s'].split(',').each do |item| + collector.matchers.first.got['s'].split(',').each do |item| some_plan, some_band, value = *item.split('-') return value.to_i if some_plan.to_i == plan.to_i && some_band.to_i == band.to_i end @@ -368,7 +368,7 @@ def with_alarm_activated task, site, alarm_code_id, initial_deactivation: true collect_task = task.async do # run the collector in an async task collector = RSMP::AlarmCollector.new( site, num: 1, - query: { + matcher: { 'cId' => component_id, 'aCId' => alarm_code_id, 'aSp' => alarm_specialization, @@ -386,7 +386,7 @@ def with_alarm_activated task, site, alarm_code_id, initial_deactivation: true collect_task = task.async do # run the collector in an async task collector = RSMP::AlarmCollector.new( site, num: 1, - query: { + matcher: { 'cId' => component_id, 'aCId' => alarm_code_id, 'aSp' => /Issue/i, @@ -493,7 +493,7 @@ def verify_startup_sequence &block component = Validator.get_config('main_component') timeout = Validator.get_config('timeouts','startup_sequence') collector = RSMP::StatusCollector.new @site, status_list, timeout: timeout - sequencer = Validator::StatusHelpers::SequenceHelper.new Validator.get_config('startup_sequence') + sequencer = Validator::StatusHelpers::SignalGroupSequenceHelper.new Validator.get_config('startup_sequence') states = nil collector_task = @task.async do @@ -575,7 +575,7 @@ def suspend_alarm site, task, cId:, aCId:, collect: RSMP::AlarmCollector.new(site, m_id: suspend.m_id, num: 1, - query: { + matcher: { 'cId' => cId, 'aCI' => aCId, 'aSp' => 'Suspend', @@ -603,7 +603,7 @@ def resume_alarm site, task, cId:, aCId:, collect: RSMP::AlarmCollector.new(site, m_id: resume.m_id, num: 1, - query: { + matcher: { 'cId' => cId, 'aCI'=>aCId, 'aSp'=>'Suspend', diff --git a/spec/support/formatters/steps.rb b/spec/support/formatters/steps.rb new file mode 100644 index 00000000..1b25d65b --- /dev/null +++ b/spec/support/formatters/steps.rb @@ -0,0 +1,59 @@ +require 'rspec/core/formatters/console_codes' + +module Validator + class Steps < FormatterBase + RSpec::Core::Formatters.register self, :start, :dump_pending, :dump_failures, :close, + :dump_summary, :example_started, :example_passed, :example_failed, :example_pending, + :message, :warning, :step, :example_group_started, :example_group_finished + + def start notification + @output << "\n" + end + + def warning notification + @output << colorize(" Warning: #{notification.message}\n",:yellow) + end + + def step notification + @output << colorize(" #{notification.message}\n",:cyan) + end + + def message notification + @output << " #{notification.message}\n" + end + + def example_pending notification + @output << colorize(" Pending\n\n", :pending) + end + + def example_started notification + @output << colorize("\n#{notification.example.full_description}\n",:bold) + end + + def example_passed notification # ExampleNotification + @output << colorize(" Passed\n\n",:success) + end + + def example_failed notification + # RSpec::Core::Formatters::ExceptionPresenter is a private class which + # should not really be used by us, but the snippet extraction and backtrace + # processing seems rather cumbersome to reimplement + presenter = RSpec::Core::Formatters::ExceptionPresenter.new(notification.example.execution_result.exception, notification.example, :indentation => 0) + + error = presenter.message_lines.map do |line| + colorize(" #{line}\n",:failure) + end.join + @output << error << "\n" + + backtrace = presenter.formatted_backtrace.map do |line| + colorize(" # #{line}\n",:light_blue) + end.join + @output << backtrace << "\n" + end + + def dump_pending notification + dump_sentinel_warnings + super notification + end + end +end diff --git a/spec/support/sequence_helper.rb b/spec/support/signal_group_sequence_helper.rb similarity index 97% rename from spec/support/sequence_helper.rb rename to spec/support/signal_group_sequence_helper.rb index 49afb6dd..7215d9e0 100644 --- a/spec/support/sequence_helper.rb +++ b/spec/support/signal_group_sequence_helper.rb @@ -1,5 +1,5 @@ module Validator::StatusHelpers - class SequenceHelper + class SignalGroupSequenceHelper attr_reader :sequence, :latest def initialize sequence diff --git a/spec/support/signal_priority_request_helper.rb b/spec/support/signal_priority_request_helper.rb new file mode 100644 index 00000000..4e66034d --- /dev/null +++ b/spec/support/signal_priority_request_helper.rb @@ -0,0 +1,141 @@ +module Validator::StatusHelpers + + # Match a specific status response or update + class S0033Matcher < RSMP::StatusMatcher + + attr_accessor :state + def initialize want, request_id:, state: nil + super want + @request_id = request_id + @state = state + @latest_state = nil + end + + # Match a status value against a matcher + def match? item + super_matched = super(item) + if super_matched == true + state = find_request_state item['s'] + if state == @state.to_s && state != @latest_state + @latest_state = state + true + else + false + end + else + super_matched + end + end + + # look through a status message to find state + # updates for a specific priority request + def find_request_state list + priority = list.find { |prio| prio['r'] == @request_id } + priority['s'] if priority + end + + end + + class SignalPriorityRequestHelper < RSMP::Queue + + include Validator::StatusHelpers + include Validator::CommandHelpers + + def initialize site, component:, signal_group_id:, timeout:, task: + super( site, + filter: RSMP::Filter.new( + type: 'StatusUpdate', + ingoing: true, + outgoing: false, + component: component + ), + task: task + ) + @site = site + @component = component + @signal_group_id = signal_group_id + @request_id = SecureRandom.uuid()[0..3] + @matcher = S0033Matcher.new({"cCI"=>"S0033", "q"=>"recent"}, request_id: @request_id) + @subscribe_list = [{'sCI'=>'S0033','n'=>'status','uRt'=>'0'}] + @subscribe_list.map! { |item| item.merge!('sOc' => true) } if use_sOc?(@site) + @unsubscribe_list = [{'sCI'=>'S0033','n'=>'status'}] + @got = [] + @timeout = timeout + end + + def run &block + start + yield + ensure + stop + end + + def request( + level: 7, + eta: 2, + vehicleType: 'car' + ) + command_list = build_command_list(:M0022, :requestPriority, { + requestId: @request_id, + signalGroupId: @signal_group_id, + type: 'new', + level: level, + eta: eta, + vehicleType: vehicleType + }) + @site.send_command @component, command_list + end + + def request_unrelated( + level: 7, + eta: 2, + vehicleType: 'car' + ) + command_list = build_command_list( :M0022, :requestPriority, { + requestId: SecureRandom.uuid()[0..3], + signalGroupId: @signal_group_id, + type: 'new', + level: level, + eta: eta, + vehicleType: vehicleType + }) + @site.send_command @component, command_list + end + + def cancel + command_list = build_command_list :M0022, :requestPriority, { + requestId: @request_id, + type: 'cancel' + } + @site.send_command @component, command_list + end + + def expect state + @matcher.state = state + message = wait_for_message timeout: @timeout + rescue RSMP::TimeoutError + raise RSMP::TimeoutError.new("Priority request did not reach state #{state} within #{@timeout}s") + end + + private + + def accept_message? message + super && get_items(message).any? {|item| @matcher.match?(item) } + end + + def start + start_receiving + @site.subscribe_to_status @component, @subscribe_list + end + + def stop + @site.unsubscribe_to_status @component, @unsubscribe_list + stop_receiving + end + + def get_items message + message.attributes['sS'] || [] + end + end +end +