inferno_core 1.4.2 → 1.4.4

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/cli/execute_script.rb +8 -4
  3. data/lib/inferno/apps/cli/main.rb +46 -5
  4. data/lib/inferno/apps/cli/requirements_coverage_checker.rb +1 -1
  5. data/lib/inferno/apps/cli/templates/Rakefile.tt +0 -13
  6. data/lib/inferno/apps/cli/templates/config/nginx.background.conf.tt +0 -30
  7. data/lib/inferno/apps/cli/templates/config/nginx.conf.tt +0 -30
  8. data/lib/inferno/apps/cli/templates/docker-compose.background.yml.tt +1 -14
  9. data/lib/inferno/apps/cli/templates/docker-compose.yml.tt +1 -10
  10. data/lib/inferno/apps/cli/templates/execution_scripts/{README.md.tt → README.md} +3 -3
  11. data/lib/inferno/apps/cli/templates/lib/%library_name%/requirements/%library_name%_requirements.csv +3 -0
  12. data/lib/inferno/apps/cli/templates/lib/%library_name%/requirements/Inferno Requirements Template.xlsx +0 -0
  13. data/lib/inferno/apps/cli/templates/lib/%library_name%/requirements/README.md +12 -0
  14. data/lib/inferno/apps/cli/templates/lib/%library_name%/requirements/generated/%test_suite_id%_requirements_coverage.csv.tt +1 -0
  15. data/lib/inferno/dsl/fhir_resource_navigation.rb +12 -2
  16. data/lib/inferno/dsl/fhir_resource_validation.rb +186 -4
  17. data/lib/inferno/dsl/must_support_assessment.rb +19 -8
  18. data/lib/inferno/dsl/must_support_metadata_extractor.rb +44 -1
  19. data/lib/inferno/dsl/profile_metadata.rb +113 -0
  20. data/lib/inferno/dsl/resume_test_route.rb +4 -1
  21. data/lib/inferno/dsl/suite_endpoint.rb +164 -6
  22. data/lib/inferno/dsl.rb +1 -0
  23. data/lib/inferno/entities/ig.rb +51 -8
  24. data/lib/inferno/public/bundle.js +34 -34
  25. data/lib/inferno/public/bundle.js.LICENSE.txt +0 -2
  26. data/lib/inferno/test_runner.rb +55 -40
  27. data/lib/inferno/utils/execution_script_runner.rb +56 -7
  28. data/lib/inferno/version.rb +1 -1
  29. data/spec/spec_helper.rb +18 -0
  30. metadata +7 -4
  31. data/lib/inferno/apps/cli/templates/lib/%library_name%/suite.rb.tt +0 -59
@@ -1,5 +1,3 @@
1
- /*! js-yaml 4.2.0 https://github.com/nodeca/js-yaml @license MIT */
2
-
3
1
  /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
4
2
 
5
3
  /**
@@ -59,51 +59,66 @@ module Inferno
59
59
  end
60
60
 
61
61
  def run_test(test, scratch)
62
- inputs = load_inputs(test)
63
- input_json_string = inputs_as_json(test, inputs)
64
-
65
- test_instance =
66
- test.new(
67
- inputs:,
68
- test_session_id: test_session.id,
69
- scratch:,
70
- suite_options: test_session.suite_options_hash
71
- )
72
-
73
- result = evaluate_runnable_result(test, test_instance, inputs)
74
-
75
- outputs = save_outputs(test_instance)
76
- output_json_string = JSON.generate(outputs)
77
-
78
- result_params = {
79
- messages: test_instance.messages,
80
- requests: test_instance.requests,
81
- result:,
82
- result_message: test_instance.result_message,
83
- input_json: input_json_string,
84
- output_json: output_json_string
85
- }.merge(test.reference_hash)
86
-
87
- test_result =
88
- if result == 'wait'
89
- # The waiting status and the wait result must become visible to
90
- # readers atomically, so that pollers never see the run waiting
91
- # without the waiting test's result being present.
92
- Inferno::Application['db.connection'].transaction do
93
- test_runs_repo.mark_as_waiting(test_run.id, test_instance.identifier, test_instance.wait_timeout)
62
+ around_test(test) do
63
+ inputs = load_inputs(test)
64
+ input_json_string = inputs_as_json(test, inputs)
65
+
66
+ test_instance =
67
+ test.new(
68
+ inputs:,
69
+ test_session_id: test_session.id,
70
+ scratch:,
71
+ suite_options: test_session.suite_options_hash
72
+ )
73
+
74
+ result = evaluate_runnable_result(test, test_instance, inputs)
75
+
76
+ outputs = save_outputs(test_instance)
77
+ output_json_string = JSON.generate(outputs)
78
+
79
+ result_params = {
80
+ messages: test_instance.messages,
81
+ requests: test_instance.requests,
82
+ result:,
83
+ result_message: test_instance.result_message,
84
+ input_json: input_json_string,
85
+ output_json: output_json_string
86
+ }.merge(test.reference_hash)
87
+
88
+ test_result =
89
+ if result == 'wait'
90
+ # The waiting status and the wait result must become visible to
91
+ # readers atomically, so that pollers never see the run waiting
92
+ # without the waiting test's result being present.
93
+ Inferno::Application['db.connection'].transaction do
94
+ test_runs_repo.mark_as_waiting(test_run.id, test_instance.identifier, test_instance.wait_timeout)
95
+ persist_result(result_params)
96
+ end
97
+ else
94
98
  persist_result(result_params)
95
99
  end
96
- else
97
- persist_result(result_params)
98
- end
99
100
 
100
- # If running a single test, update its parents' results. If running a
101
- # group or suite, #run_group handles updating the parents.
102
- return test_result if test_run.test_id.blank?
101
+ # If running a single test, update its parents' results. If running a
102
+ # group or suite, #run_group handles updating the parents.
103
+ update_parent_result(test.parent) if test_run.test_id.present?
103
104
 
104
- update_parent_result(test.parent)
105
+ test_result
106
+ end
107
+ end
105
108
 
106
- test_result
109
+ # Wraps the execution of a single test. Prepend a module overriding this method
110
+ # to run instrumentation around each test, for example to emit a distinct trace,
111
+ # span, or timing metric per test instead of one that spans the whole run.
112
+ #
113
+ # An override must call `super` exactly once and return its value unchanged:
114
+ # it is the test's result, and parent results are rolled up from it. An
115
+ # override that does not call `super` silently skips the test.
116
+ #
117
+ # @param test [Class] the test being run (an Inferno::Entities::Test subclass)
118
+ # @yield executes the test and returns its result
119
+ # @return the value returned by the block
120
+ def around_test(_test)
121
+ yield
107
122
  end
108
123
 
109
124
  def check_inputs(test, _test_instance, inputs)
@@ -3,7 +3,11 @@ require 'open3'
3
3
  module Inferno
4
4
  module Utils
5
5
  module ExecutionScriptRunner
6
- def self.run_all(pattern: 'execution_scripts/**/*.yaml', inferno_base_url: nil, allow_known_errors: false)
6
+ def self.run_all(pattern: 'execution_scripts/**/*.yaml', inferno_base_url: nil,
7
+ allow_known_errors: false, allow_commands: false,
8
+ compare_messages: true, compare_result_message: true,
9
+ poll_interval: 3, default_poll_timeout: 120,
10
+ only_different_messages: true)
7
11
  scripts = Dir.glob(pattern)
8
12
 
9
13
  if scripts.empty?
@@ -22,7 +26,10 @@ module Inferno
22
26
  next
23
27
  end
24
28
 
25
- result = run_script(config, inferno_base_url:, allow_known_errors:)
29
+ result = run_script(config, inferno_base_url:, allow_known_errors:, allow_commands:,
30
+ compare_messages:, compare_result_message:,
31
+ poll_interval:, default_poll_timeout:,
32
+ only_different_messages:)
26
33
  (result == :pass ? passed : failed) << config
27
34
 
28
35
  puts
@@ -31,19 +38,61 @@ module Inferno
31
38
  print_summary(passed, failed)
32
39
  end
33
40
 
34
- def self.run_script(config, inferno_base_url:, allow_known_errors:)
41
+ def self.run_script(config, inferno_base_url:, allow_known_errors:, allow_commands: false,
42
+ compare_messages: true, compare_result_message: true,
43
+ poll_interval: 3, default_poll_timeout: 120,
44
+ only_different_messages: true)
35
45
  puts '=' * 60
36
46
  puts "Running: #{config}"
37
47
  puts '=' * 60
38
48
 
39
- allow_commands = File.basename(config, '.yaml').include?('_with_commands')
49
+ allow_commands ||= File.basename(config, '.yaml').include?('_with_commands')
50
+ cmd = build_command(config, inferno_base_url:, allow_commands:, compare_messages:,
51
+ compare_result_message:, poll_interval:, default_poll_timeout:,
52
+ only_different_messages:)
53
+ output, exitstatus = stream_command(*cmd)
54
+
55
+ determine_result(config, exitstatus, output, allow_known_errors)
56
+ end
57
+
58
+ # @private
59
+ def self.build_command(config, inferno_base_url:, allow_commands:, compare_messages:,
60
+ compare_result_message:, poll_interval:, default_poll_timeout:,
61
+ only_different_messages:)
40
62
  cmd = ['bundle', 'exec', 'inferno', 'execute_script', config]
41
63
  cmd += ['--inferno-base-url', inferno_base_url] if inferno_base_url
42
64
  cmd += ['--allow-commands'] if allow_commands
43
- output, status = Open3.capture2e(*cmd)
44
- puts output
65
+ cmd += ['--poll-interval', poll_interval.to_s] if poll_interval != 3
66
+ cmd += ['--default-poll-timeout', default_poll_timeout.to_s] if default_poll_timeout != 120
67
+ cmd + comparison_flags(compare_messages:, compare_result_message:, only_different_messages:)
68
+ end
69
+
70
+ # @private
71
+ def self.comparison_flags(compare_messages:, compare_result_message:, only_different_messages:)
72
+ flags = []
73
+ flags << '--no-compare-messages' unless compare_messages
74
+ flags << '--no-compare-result-message' unless compare_result_message
75
+ flags << '--no-only-different-messages' unless only_different_messages
76
+ flags
77
+ end
78
+
79
+ # Runs +cmd+ as a subprocess, echoing its combined stdout/stderr to the console
80
+ # line-by-line as it's produced (rather than buffering until the process exits),
81
+ # while still returning the full captured output for result determination.
82
+ def self.stream_command(*cmd)
83
+ output = +''
84
+ exitstatus = nil
85
+
86
+ Open3.popen2e(*cmd) do |stdin, stdout_and_stderr, wait_thread|
87
+ stdin.close
88
+ stdout_and_stderr.each_line do |line|
89
+ puts line
90
+ output << line
91
+ end
92
+ exitstatus = wait_thread.value.exitstatus
93
+ end
45
94
 
46
- determine_result(config, status.exitstatus, output, allow_known_errors)
95
+ [output, exitstatus]
47
96
  end
48
97
 
49
98
  def self.determine_result(config, return_code, output, allow_known_errors)
@@ -1,4 +1,4 @@
1
1
  module Inferno
2
2
  # Standard patterns for gem versions: https://guides.rubygems.org/patterns/
3
- VERSION = '1.4.2'.freeze
3
+ VERSION = '1.4.4'.freeze
4
4
  end
data/spec/spec_helper.rb CHANGED
@@ -128,6 +128,24 @@ RSpec.configure do |config|
128
128
  DatabaseCleaner.cleaning { example.run }
129
129
  end
130
130
 
131
+ # Inferno::Entities::Test/TestGroup/TestSuite are meant to be used only as
132
+ # abstract bases -- real tests/groups/suites are always DSL-defined subclasses.
133
+ # Some specs instantiate the bare classes directly (e.g. `Inferno::Test.new`) as
134
+ # lightweight doubles. If anything then reads `.id` on one of those, `Runnable#id`
135
+ # permanently memoizes @id/@base_id/@database_id onto that *shared* class object
136
+ # (since it's a genuinely named Ruby class, `default_id` returns its `Module#name`).
137
+ # Left in place, that id leaks into every subsequently-defined anonymous subclass
138
+ # via `copy_instance_variables`, causing DuplicateEntityIdException collisions in
139
+ # unrelated, later-running examples. Reset it after every example so it can never
140
+ # survive to pollute a different example.
141
+ config.after do
142
+ [Inferno::Entities::Test, Inferno::Entities::TestGroup, Inferno::Entities::TestSuite].each do |klass|
143
+ klass.instance_variable_set(:@id, nil)
144
+ klass.instance_variable_set(:@base_id, nil)
145
+ klass.instance_variable_set(:@database_id, nil)
146
+ end
147
+ end
148
+
131
149
  config.include FactoryBot::Syntax::Methods
132
150
 
133
151
  config.before(:suite) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inferno_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen MacVicar
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-27 00:00:00.000000000 Z
13
+ date: 2026-09-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -556,7 +556,7 @@ files:
556
556
  - lib/inferno/apps/cli/templates/docs/_Sidebar.md
557
557
  - lib/inferno/apps/cli/templates/execution_scripts/%library_name%_script.yaml.tt
558
558
  - lib/inferno/apps/cli/templates/execution_scripts/%library_name%_script_expected.json.tt
559
- - lib/inferno/apps/cli/templates/execution_scripts/README.md.tt
559
+ - lib/inferno/apps/cli/templates/execution_scripts/README.md
560
560
  - lib/inferno/apps/cli/templates/lib/%library_name%.rb.tt
561
561
  - lib/inferno/apps/cli/templates/lib/%library_name%/example_suite.rb.tt
562
562
  - lib/inferno/apps/cli/templates/lib/%library_name%/example_suite/patient_group.rb.tt
@@ -564,9 +564,11 @@ files:
564
564
  - lib/inferno/apps/cli/templates/lib/%library_name%/igs/README.md
565
565
  - lib/inferno/apps/cli/templates/lib/%library_name%/igs/put_ig_package_dot_tgz_here
566
566
  - lib/inferno/apps/cli/templates/lib/%library_name%/metadata.rb.tt
567
+ - lib/inferno/apps/cli/templates/lib/%library_name%/requirements/%library_name%_requirements.csv
567
568
  - lib/inferno/apps/cli/templates/lib/%library_name%/requirements/Inferno Requirements
568
569
  Template.xlsx
569
- - lib/inferno/apps/cli/templates/lib/%library_name%/suite.rb.tt
570
+ - lib/inferno/apps/cli/templates/lib/%library_name%/requirements/README.md
571
+ - lib/inferno/apps/cli/templates/lib/%library_name%/requirements/generated/%test_suite_id%_requirements_coverage.csv.tt
570
572
  - lib/inferno/apps/cli/templates/lib/%library_name%/version.rb.tt
571
573
  - lib/inferno/apps/cli/templates/run.sh
572
574
  - lib/inferno/apps/cli/templates/setup.sh
@@ -678,6 +680,7 @@ files:
678
680
  - lib/inferno/dsl/must_support_metadata_extractor.rb
679
681
  - lib/inferno/dsl/oauth_credentials.rb
680
682
  - lib/inferno/dsl/primitive_type.rb
683
+ - lib/inferno/dsl/profile_metadata.rb
681
684
  - lib/inferno/dsl/request_storage.rb
682
685
  - lib/inferno/dsl/requirement_set.rb
683
686
  - lib/inferno/dsl/result_collection.rb
@@ -1,59 +0,0 @@
1
- require_relative 'metadata'
2
- require_relative 'patient_group'
3
-
4
- module <%= module_name %>
5
- class Suite < Inferno::TestSuite
6
- id :<%= test_suite_id %>
7
- title '<%= title_name %> Test Suite'
8
- description '<%= human_name %> test suite.'
9
-
10
- # These inputs will be available to all tests in this suite
11
- input :url,
12
- title: 'FHIR Server Base Url'
13
-
14
- input :credentials,
15
- title: 'OAuth Credentials',
16
- type: :auth_info,
17
- optional: true
18
-
19
- # All FHIR requests in this suite will use this FHIR client
20
- fhir_client do
21
- url :url
22
- auth_info :credentials
23
- end
24
-
25
- # All FHIR validation requests will use this FHIR validator
26
- fhir_resource_validator do
27
- # igs 'identifier#version' # Use this method for published IGs/versions
28
- # igs 'igs/filename.tgz' # Use this otherwise
29
-
30
- exclude_message do |message|
31
- message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
32
- end
33
- end
34
-
35
- # Tests and TestGroups can be defined inline
36
- group do
37
- id :capability_statement
38
- title 'Capability Statement'
39
- description 'Verify that the server has a CapabilityStatement'
40
-
41
- test do
42
- id :capability_statement_read
43
- title 'Read CapabilityStatement'
44
- description 'Read CapabilityStatement from /metadata endpoint'
45
-
46
- run do
47
- fhir_get_capability_statement
48
-
49
- assert_response_status(200)
50
- assert_resource_type(:capability_statement)
51
- end
52
- end
53
- end
54
-
55
- # Tests and TestGroups can be written in separate files and then included
56
- # using their id
57
- group from: :patient_group
58
- end
59
- end