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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55ee531553c2e8a6e8a4e92a88a225c84d9eae9cc44226c5ecc57f7c51be87fa
4
- data.tar.gz: '09a94572268f042111a5799c6c0a5248445eb22422110d624b6c4d455b45cadf'
3
+ metadata.gz: bf28fcdaff9ccf8df4f107cbe2f045e55316f81285c21520da2ef63a19f9f94f
4
+ data.tar.gz: 5c7614286acbcb9dce4abb36b66fb48a94493b072a7f88432b598e3249d0b6df
5
5
  SHA512:
6
- metadata.gz: 6fc22fafb1e68d93fc092fd90cfd8598892f2a19369dc4b50ca8f3ce4bf524418d08d7e996e05da22ac30e4f937ab0305816572af598338dbebea956ceb42d53
7
- data.tar.gz: 4f39f0305b3d3a779e1f1c3355f4839f6790fbca9eb8bf049fea8694d85a29688242dd3ef5b9a350d5187af59e7f95a88894f14f8169eeae3b6d437674cd98a7
6
+ metadata.gz: 3e31a7dd63d4143b7804f04026d090a1936e3918a6bd2f060c6e6dd8ae81ad74600ac76cac553a17a89daad2bf769efeaf293f0c6f73d5ca049dbcc756f99e06
7
+ data.tar.gz: 4abbb6813ee551793997e18e680f131f15680f2a2a16361d524607baf9068807e4f7a663e5d136e2941b9dad8c9601a3ba995dcf80dcc778fef365702e34aa01
@@ -1,6 +1,12 @@
1
1
  # Config for testing Cross RS4S traffic light controller.
2
2
  # The settings are used for starting a local supervisor listening for the site tested
3
3
  # See https://rsmp-nordic.github.io/rsmp_validator/config/
4
+ compliance:
5
+ id: cross-rs4s
6
+ kind: site
7
+ name: Cross RS4S/RS4T/RS5
8
+ product_url: https://www.cross-traffic.com/en/traffic-light-controllers/
9
+
4
10
  local_supervisor:
5
11
  ips: all
6
12
  max_sites: 1
@@ -1,5 +1,11 @@
1
1
  # Config for testing a supervisor running on localhost (e.g. one from the rsmp gem)
2
2
  # The settings are used for starting a local site connecting to the supervisor tested
3
+ compliance:
4
+ id: gem-supervisor
5
+ kind: supervisor
6
+ name: RSMP Nordic CLI supervisor
7
+ product_url: https://github.com/rsmp-nordic/rsmp
8
+
3
9
  local_site:
4
10
  type: tlc
5
11
  site_id: RN+SI0001
data/config/gem_tlc.yaml CHANGED
@@ -1,6 +1,12 @@
1
1
  # Config for testing RSMP Nordic traffic light controller emulator.
2
2
  # The settings are used for starting a local supervisor listening for the site tested
3
3
  # See https://rsmp-nordic.github.io/rsmp_validator/config/
4
+ compliance:
5
+ id: gem-tlc
6
+ kind: site
7
+ name: RSMP Nordic CLI TLC
8
+ product_url: https://github.com/rsmp-nordic/rsmp
9
+
4
10
  local_supervisor:
5
11
  port: 13111
6
12
  ips: all
@@ -1,13 +1,20 @@
1
1
  # Config for testing Kapsch ETX traffic light controller.
2
2
  # The settings are used for starting a local supervisor listening for the site tested
3
3
  # See https://rsmp-nordic.github.io/rsmp_validator/config/
4
+ compliance:
5
+ id: kapsch-etx
6
+ kind: site
7
+ name: Kapsch EcoTrafiX
8
+ product_url: https://www.kapsch.net/_Resources/Persistent/f9e0a0525985dc4a9d0b3cb6b67476c0b1257b1c/Kapsch_EcoTrafiX-Controller-16_Datasheet-EN.pdf
9
+
4
10
  local_supervisor:
5
11
  ips: all
6
12
  max_sites: 1
7
- guest:
8
- intervals:
9
- timer: 1 # main validator timer resolution in seconds
10
- watchdog: 60 # send watchdog every x seconds
13
+ sites:
14
+ default:
15
+ intervals:
16
+ timer: 1 # main validator timer resolution in seconds
17
+ watchdog: 60 # send watchdog every x seconds
11
18
  sxls:
12
19
  tlc: "1.2.1"
13
20
  timeouts: # Timeouts used when waiting for messages from site.
@@ -50,5 +57,5 @@ alarm_triggers:
50
57
  component: KTT+TC=101DL004
51
58
  secrets:
52
59
  security_codes:
53
- 1: "1111"
54
- 2: "2222"
60
+ 1: '1111'
61
+ 2: '2222'
@@ -1,6 +1,12 @@
1
1
  # Configuration for testing Lightmotion Satellite RSMP proxy software
2
2
  # The settings are used for starting a local supervisor listening for the site tested
3
3
  # See https://rsmp-nordic.github.io/rsmp_validator/config/
4
+ compliance:
5
+ id: lightmotion-satellite
6
+ kind: site
7
+ name: Lightmotion Satellite
8
+ product_url: https://lightmotion.fi/
9
+
4
10
  local_supervisor:
5
11
  ips: all
6
12
  max_sites: 1
@@ -52,5 +58,5 @@ alarm_triggers:
52
58
  component: AA+BBCCC=DDDDL001
53
59
  secrets:
54
60
  security_codes:
55
- 1: "1234"
56
- 2: "4321"
61
+ 1: '1234'
62
+ 2: '4321'
@@ -1,6 +1,12 @@
1
1
  # Config for testing Cross RS4S traffic light controller.
2
2
  # The settings are used for starting a local supervisor listening for the site tested
3
3
  # See https://rsmp-nordic.github.io/rsmp_validator/config/
4
+ compliance:
5
+ id: semaforica-cartesio
6
+ kind: site
7
+ name: La Semaforica TECSEN Cartesio
8
+ product_url: https://lasemaforica.com/en/products/cartesio-highest-processing-power-interactive-traffic-controllers/
9
+
4
10
  local_supervisor:
5
11
  ips: all
6
12
  max_sites: 1
@@ -52,5 +58,5 @@ alarm_triggers:
52
58
  component: CAR_DL_016
53
59
  secrets:
54
60
  security_codes:
55
- 1: 1234
56
- 2: 12341234
61
+ 1: '1234'
62
+ 2: '12341234'
data/config/sus.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  # bundle exec sus test/validator
7
7
  #
8
8
  # To run conformance tests against real equipment, use the rsmp-validator executable:
9
- # bundle exec rsmp-validator
9
+ # bundle exec rsmp-validator run test/site
10
10
 
11
11
  def test_paths
12
12
  Dir.glob('test/validator/**/*.rb', base: @root)
@@ -1,6 +1,12 @@
1
1
  # Config for testing Swarco ITC-3 traffic light controller.
2
2
  # The settings are used for starting a local supervisor listening for the site tested
3
3
  # See https://rsmp-nordic.github.io/rsmp_validator/config/
4
+ compliance:
5
+ id: swarco-itc3
6
+ kind: site
7
+ name: Swarco ITC-3
8
+ product_url: https://www.swarco.com/products/traffic-light-controllers/itc-3-traffic-controller
9
+
4
10
  local_supervisor:
5
11
  ips: all
6
12
  max_sites: 1
@@ -51,5 +57,5 @@ alarm_triggers:
51
57
  component: AA+BBCCC=DDDDL002
52
58
  secrets:
53
59
  security_codes:
54
- 1: default
55
- 2: default
60
+ 1: 'default'
61
+ 2: 'default'
@@ -1,5 +1,11 @@
1
1
  # The settings are used for starting a local tlc emulator connecting to the supervisor system to be tested
2
2
 
3
+ compliance:
4
+ id: tecsen-tmacs
5
+ kind: supervisor
6
+ name: TECSEN TMacs
7
+ product_url: https://www.tmacs.it/en/
8
+
3
9
  local_site:
4
10
  type: tlc
5
11
  site_id: CAR_TC_001
data/exe/rsmp-validator CHANGED
@@ -1,121 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler/setup'
4
- require 'sus'
5
- require 'sus/config'
3
+ require_relative '../lib/rsmp/validator/cli/entrypoint'
6
4
 
7
- def print_help
8
- puts <<~HELP
9
- RSMP Validator runs conformance tests against RSMP sites and supervisors.
10
- Full documentation: https://rsmp-nordic.github.io/rsmp_validator/
11
-
12
- Usage:
13
- rsmp-validator [OPTIONS] PATH...
14
-
15
- Examples:
16
- rsmp-validator test/site
17
- rsmp-validator test/site/core
18
- rsmp-validator test/supervisor/connect_spec.rb
19
-
20
- Options:
21
- --verbose Show detailed sus output.
22
- --log Print RSMP log output to stdout.
23
- --log PATH Write RSMP log output to PATH.
24
- -h, --help Show this help.
25
-
26
- Environment:
27
- SITE_CONFIG Path to site test configuration.
28
- SUPERVISOR_CONFIG Path to supervisor test configuration.
29
- AUTO_SITE_CONFIG Path to local site configuration to start automatically.
30
- HELP
31
- end
32
-
33
- if ARGV.first == 'help' || ARGV.include?('--help') || ARGV.include?('-h')
34
- print_help
35
- exit 0
36
- end
37
-
38
- # Parse --log [path] from ARGV before Sus::Config.load consumes remaining args.
39
- # --log => RSMP logs to $stdout
40
- # --log <path> => RSMP logs to file, sus output to console (brief)
41
- # + --verbose => sus output also goes to file (interleaved, perfectly ordered)
42
- # (no --log) => RSMP logs suppressed
43
- log_index = ARGV.index('--log')
44
- log_path = nil
45
- log_to_stdout = false
46
- if log_index
47
- ARGV.delete_at(log_index)
48
- if ARGV[log_index] && !ARGV[log_index].start_with?('-')
49
- log_path = ARGV.delete_at(log_index)
50
- else
51
- log_to_stdout = true
52
- end
53
- end
54
-
55
- # Writes to two IOs simultaneously through a single object, so both RSMP messages
56
- # and Sus verbose output share one file descriptor and are perfectly interleaved.
57
- class TeeIO
58
- def initialize(primary, secondary)
59
- @primary = primary
60
- @secondary = secondary
61
- end
62
-
63
- def write(*args)
64
- @primary.write(*args)
65
- @secondary.write(*args)
66
- end
67
-
68
- def puts(*args)
69
- @primary.puts(*args)
70
- @secondary.puts(*args)
71
- end
72
-
73
- def flush
74
- @primary.flush
75
- @secondary.flush
76
- end
77
-
78
- def isatty = @primary.isatty
79
- def tty? = @primary.tty?
80
- end
81
-
82
- # Load config/validator.rb instead of config/sus.rb so that conformance tests
83
- # live in valid/site/ and valid/supervisor/, keeping them separate from the
84
- # internal tests in test/ that plain `sus` runs.
85
- validator_config_class = Class.new(Sus::Config) do
86
- attr_accessor :log_to_stdout, :log_path, :log_file_io
87
-
88
- def self.path(root)
89
- path = File.join(root, 'config/validator.rb')
90
- File.exist?(path) ? path : nil
91
- end
92
- end
93
-
94
- config = validator_config_class.load
95
- config.log_to_stdout = log_to_stdout
96
- config.log_path = log_path
97
-
98
- registry = config.registry
99
-
100
- def run_suite(config, registry, output, verbose)
101
- assertions = Sus::Assertions.default(output: output, verbose: verbose)
102
- config.before_tests(assertions)
103
- registry.call(assertions)
104
- config.after_tests(assertions)
105
- exit(1) unless assertions.passed?
106
- end
107
-
108
- # When --log <path> and --verbose are both given, open the file once and share
109
- # the handle: Sus writes via TeeIO (terminal + file), RSMP writes to the same
110
- # File object directly. One fd => serialized writes => perfect ordering.
111
- if log_path && config.verbose?
112
- File.open(log_path, 'w') do |log_file|
113
- config.log_path = nil
114
- config.log_file_io = log_file
115
- output = Sus::Output.default(TeeIO.new($stderr, log_file))
116
- run_suite(config, registry, output, true)
117
- end
118
- else
119
- output = config.verbose? ? config.output : Sus::Output::Null.new
120
- run_suite(config, registry, output, config.verbose?)
121
- end
5
+ RSMP::Validator::CLI.start(ARGV)
@@ -3,12 +3,13 @@ module RSMP
3
3
  # Sus fixture module that runs each test inside the shared Async reactor.
4
4
  # Include this in the sus base class to ensure all tests run within the reactor context.
5
5
  module AsyncContext
6
- def around
7
- RSMP::Validator.reactor.run do |_task|
8
- yield
6
+ def around(&block)
7
+ task = RSMP::Validator.reactor.run do |_task|
8
+ catch(:rsmp_validator_test_failure, &block)
9
9
  ensure
10
10
  RSMP::Validator.reactor.interrupt
11
11
  end
12
+ task.wait
12
13
  end
13
14
  end
14
15
  end
@@ -19,25 +19,15 @@ module RSMP
19
19
  return if @node
20
20
 
21
21
  @node = build_node
22
-
23
- @task = Async do |task|
24
- task.annotate "auto_#{node_type}"
25
- log("Starting auto #{node_type}")
26
- @node.start
27
- rescue Async::TimeoutError
28
- raise RSMP::TimeoutError, "Timeout while starting auto #{node_type}"
29
- end
22
+ log("Starting auto #{node_type}")
23
+ @task = @node.start(parent: Async::Task.current)
30
24
  end
31
25
 
32
26
  # Stop the auto node
33
27
  def stop
34
- if @node
35
- log("Stopping auto #{node_type}")
36
- @node.ignore_errors RSMP::DisconnectError do
37
- @node.stop
38
- end
39
- end
40
- @task&.stop
28
+ log("Stopping auto #{node_type}") if @node
29
+ @node&.stop
30
+ @task&.wait
41
31
  ensure
42
32
  @task = nil
43
33
  @node = nil
@@ -0,0 +1,32 @@
1
+ require 'thor'
2
+ require_relative '../config_check'
3
+
4
+ module RSMP
5
+ module Validator
6
+ # CLI subcommands for validator configuration.
7
+ class ConfigCLI < Thor
8
+ namespace :config
9
+ desc 'check PATH...', 'Validate rsmp-validator config files'
10
+ method_option :mode, type: :string, aliases: '-m', default: 'auto',
11
+ enum: %w[auto site supervisor],
12
+ banner: 'Validator config mode: auto, site or supervisor'
13
+ def check(*paths)
14
+ if paths.empty?
15
+ puts 'Error: config check requires at least one path'
16
+ exit 1
17
+ end
18
+
19
+ valid = true
20
+ paths.each do |path|
21
+ RSMP::Validator::ConfigCheck.check_file(path, mode: options[:mode])
22
+ puts 'OK'
23
+ rescue RSMP::ConfigurationError => e
24
+ valid = false
25
+ puts "Error: #{e.message}"
26
+ end
27
+
28
+ exit 1 unless valid
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'thor'
2
+ require_relative '../../validator'
3
+ require_relative 'config_cli'
4
+ require_relative 'run_options'
5
+ require_relative 'runner'
6
+
7
+ module RSMP
8
+ module Validator
9
+ # Thor entrypoint for validator commands.
10
+ class CLI < Thor
11
+ desc 'run PATH...', 'Run validator conformance tests'
12
+ method_option :verbose, type: :boolean, aliases: '-v', desc: 'Show detailed sus output'
13
+ method_option :log, type: :boolean, desc: 'Print RSMP log output to stdout'
14
+ method_option :log_path, type: :string, desc: 'Write RSMP log output to PATH'
15
+ method_option :report_json, type: :string, desc: 'Write a machine-readable compliance report to PATH'
16
+ method_option :core, type: :string, desc: 'Override RSMP Core version for this run'
17
+ method_option :sxls, type: :string, desc: 'Override SXL versions as name:version,...'
18
+ method_option :site_config, type: :string, desc: 'Use PATH as the site test config'
19
+ method_option :supervisor_config, type: :string, desc: 'Use PATH as the supervisor test config'
20
+ method_option :auto_site_config, type: :string, desc: 'Use PATH as the auto site config'
21
+ method_option :auto_supervisor_config, type: :string, desc: 'Use PATH as the auto supervisor config'
22
+ def run_tests(*paths)
23
+ run_options = RunOptions.parse(paths, thor_options: options)
24
+ if run_options[:log_to_stdout] && run_options[:log_path]
25
+ raise Thor::Error, '--log and --log-path cannot be used together'
26
+ end
27
+
28
+ status = Runner.new(run_options).run
29
+ exit status
30
+ end
31
+ map 'run' => :run_tests
32
+
33
+ register ConfigCLI, 'config', 'config COMMAND', 'Configuration commands'
34
+
35
+ def self.exit_on_failure?
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,87 @@
1
+ require 'thor'
2
+
3
+ module RSMP
4
+ module Validator
5
+ # Parses run options that Thor leaves in the path list when options appear
6
+ # after variadic test paths.
7
+ class RunOptions
8
+ FLAG_OPTIONS = {
9
+ '--verbose' => :verbose,
10
+ '-v' => :verbose,
11
+ '--log' => :log_to_stdout
12
+ }.freeze
13
+
14
+ VALUE_OPTIONS = {
15
+ '--log-path' => :log_path,
16
+ '--report-json' => :report_json_path,
17
+ '--core' => :core_version,
18
+ '--sxls' => :sxls,
19
+ '--site-config' => :site_config_path,
20
+ '--supervisor-config' => :supervisor_config_path,
21
+ '--auto-site-config' => :auto_site_config_path,
22
+ '--auto-supervisor-config' => :auto_supervisor_config_path
23
+ }.freeze
24
+
25
+ def self.parse(args, thor_options:)
26
+ new(args, thor_options).parse
27
+ end
28
+
29
+ def initialize(args, thor_options)
30
+ @args = args
31
+ @parsed = {
32
+ paths: [],
33
+ verbose: thor_options[:verbose],
34
+ log_to_stdout: thor_options[:log],
35
+ log_path: thor_options[:log_path],
36
+ report_json_path: thor_options[:report_json],
37
+ core_version: thor_options[:core],
38
+ sxls: thor_options[:sxls],
39
+ site_config_path: thor_options[:site_config],
40
+ supervisor_config_path: thor_options[:supervisor_config],
41
+ auto_site_config_path: thor_options[:auto_site_config],
42
+ auto_supervisor_config_path: thor_options[:auto_supervisor_config]
43
+ }
44
+ end
45
+
46
+ def parse
47
+ remaining = @args.dup
48
+ remaining.shift if remaining.first == 'run'
49
+ parse_remaining(remaining)
50
+ @parsed
51
+ end
52
+
53
+ private
54
+
55
+ def parse_remaining(remaining)
56
+ parse_arg(remaining.shift, remaining) until remaining.empty?
57
+ end
58
+
59
+ def parse_arg(arg, remaining)
60
+ flag_key = FLAG_OPTIONS[arg]
61
+ return @parsed[flag_key] = true if flag_key
62
+
63
+ option_key = VALUE_OPTIONS[arg]
64
+ return @parsed[option_key] = required_option_value(arg, remaining) if option_key
65
+
66
+ return parse_inline_value(arg) if arg.start_with?('--') && arg.include?('=')
67
+
68
+ @parsed[:paths] << arg
69
+ end
70
+
71
+ def parse_inline_value(arg)
72
+ option, value = arg.split('=', 2)
73
+ option_key = VALUE_OPTIONS[option]
74
+ return @parsed[option_key] = value if option_key
75
+
76
+ @parsed[:paths] << arg
77
+ end
78
+
79
+ def required_option_value(option, remaining)
80
+ value = remaining.shift
81
+ raise Thor::Error, "#{option} requires a value" if value.nil? || value.start_with?('-')
82
+
83
+ value
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,119 @@
1
+ require 'sus'
2
+ require 'sus/config'
3
+ require_relative '../compliance/report'
4
+ require_relative 'tee_io'
5
+ require_relative 'validator_overrides'
6
+
7
+ module RSMP
8
+ module Validator
9
+ # Runs the conformance test suite through Sus while preserving validator
10
+ # specific options that Sus does not know about.
11
+ class Runner
12
+ include ValidatorOverrides
13
+
14
+ def initialize(options)
15
+ @paths = options[:paths]
16
+ @verbose = options[:verbose]
17
+ @log_to_stdout = options[:log_to_stdout]
18
+ @log_path = options[:log_path]
19
+ @report_json_path = options[:report_json_path]
20
+ @overrides = options.slice(
21
+ :core_version,
22
+ :sxls,
23
+ :site_config_path,
24
+ :supervisor_config_path,
25
+ :auto_site_config_path,
26
+ :auto_supervisor_config_path
27
+ )
28
+ end
29
+
30
+ def run
31
+ with_argv(sus_args) { run_with_args }
32
+ rescue RSMP::ConnectionError
33
+ 1
34
+ end
35
+
36
+ private
37
+
38
+ def run_with_args
39
+ apply_overrides(@overrides)
40
+ config = validator_config_class.load
41
+ config.log_to_stdout = @log_to_stdout
42
+ config.log_path = @log_path
43
+
44
+ registry = config.registry
45
+ return run_with_log_file(config, registry) if @log_path && config.verbose?
46
+
47
+ run_with_default_output(config, registry)
48
+ ensure
49
+ clear_overrides(@overrides)
50
+ end
51
+
52
+ def sus_args
53
+ args = @paths.dup
54
+ args << '--verbose' if @verbose
55
+ args
56
+ end
57
+
58
+ def with_argv(args)
59
+ original = ARGV.dup
60
+ ARGV.replace(args)
61
+ yield
62
+ ensure
63
+ ARGV.replace(original)
64
+ end
65
+
66
+ def validator_config_class
67
+ Class.new(Sus::Config) do
68
+ attr_accessor :log_to_stdout, :log_path, :log_file_io
69
+
70
+ def self.path(root)
71
+ path = File.join(root, 'config/validator.rb')
72
+ File.exist?(path) ? path : nil
73
+ end
74
+ end
75
+ end
76
+
77
+ def run_with_log_file(config, registry)
78
+ File.open(@log_path, 'w') do |log_file|
79
+ config.log_path = nil
80
+ config.log_file_io = log_file
81
+ output = Sus::Output.default(TeeIO.new($stderr, log_file))
82
+ run_suite(config, registry, output, true)
83
+ end
84
+ end
85
+
86
+ def run_with_default_output(config, registry)
87
+ output = config.verbose? ? config.output : Sus::Output::Null.new
88
+ run_suite(config, registry, output, config.verbose?)
89
+ end
90
+
91
+ def run_suite(config, registry, output, verbose)
92
+ assertions = Sus::Assertions.default(output: output, verbose: verbose)
93
+ begin
94
+ config.before_tests(assertions)
95
+ registry.call(assertions)
96
+ ensure
97
+ config.after_tests(assertions)
98
+ end
99
+ write_report(assertions)
100
+ assertions.passed? ? 0 : 1
101
+ end
102
+
103
+ def write_report(assertions)
104
+ return unless @report_json_path
105
+
106
+ RSMP::Validator::Compliance::Report.write(
107
+ @report_json_path,
108
+ assertions: assertions,
109
+ env: ENV,
110
+ args: @paths,
111
+ config: RSMP::Validator.config,
112
+ config_path: RSMP::Validator.config_path,
113
+ log_path: @log_path,
114
+ report_json_path: @report_json_path
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,30 @@
1
+ module RSMP
2
+ module Validator
3
+ # Writes to two IOs simultaneously through a single object, so both RSMP
4
+ # messages and Sus verbose output can share one file descriptor.
5
+ class TeeIO
6
+ def initialize(primary, secondary)
7
+ @primary = primary
8
+ @secondary = secondary
9
+ end
10
+
11
+ def write(*args)
12
+ @primary.write(*args)
13
+ @secondary.write(*args)
14
+ end
15
+
16
+ def puts(*args)
17
+ @primary.puts(*args)
18
+ @secondary.puts(*args)
19
+ end
20
+
21
+ def flush
22
+ @primary.flush
23
+ @secondary.flush
24
+ end
25
+
26
+ def isatty = @primary.isatty
27
+ def tty? = @primary.tty?
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module RSMP
2
+ module Validator
3
+ # Applies CLI overrides to global validator settings for a single run.
4
+ module ValidatorOverrides
5
+ OVERRIDE_ACCESSORS = {
6
+ core_version: :core_version_override,
7
+ sxls: :sxls_override,
8
+ site_config_path: :site_config_path,
9
+ supervisor_config_path: :supervisor_config_path,
10
+ auto_site_config_path: :auto_site_config_path,
11
+ auto_supervisor_config_path: :auto_supervisor_config_path
12
+ }.freeze
13
+
14
+ def apply_overrides(overrides)
15
+ overrides.each do |name, value|
16
+ RSMP::Validator.public_send("#{OVERRIDE_ACCESSORS.fetch(name)}=", value)
17
+ end
18
+ end
19
+
20
+ def clear_overrides(overrides)
21
+ overrides.each_key do |name|
22
+ RSMP::Validator.public_send("#{OVERRIDE_ACCESSORS.fetch(name)}=", nil)
23
+ end
24
+ RSMP::Validator.config_path = nil
25
+ end
26
+ end
27
+ end
28
+ end