Module: Inferno::DSL::Runnable

Includes:
Utils::MarkdownFormatter
Included in:
Entities::TestGroup, Entities::TestSuite
Defined in:
lib/inferno/dsl/runnable.rb

Overview

This module contains the DSL for defining child entities in the test definition framework.

Constant Summary collapse

VARIABLES_NOT_TO_COPY =

Class instance variables are used to hold the metadata for Runnable classes. When inheriting from a Runnable class, these class instance variables need to be copied. Some instance variables should not be copied, and will need to be repopulated from scratch on the new class. Any child Runnable classes will themselves need to be subclassed so that their parent can be updated.

[
  :@id, # New runnable will have a different id
  :@parent, # New runnable unlikely to have the same parent
  :@all_children, # New subclasses have to be made for each child
  :@test_count, # Needs to be recalculated
  :@config, # Needs to be set by calling .config, which does extra work
  :@available_inputs, # Needs to be recalculated
  :@children_available_inputs # Needs to be recalculated
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::MarkdownFormatter

#format_markdown

Instance Attribute Details

#parentObject

Returns the value of attribute parent.



12
13
14
# File 'lib/inferno/dsl/runnable.rb', line 12

def parent
  @parent
end

#suite_option_requirementsObject (readonly)

Returns the value of attribute suite_option_requirements.



13
14
15
# File 'lib/inferno/dsl/runnable.rb', line 13

def suite_option_requirements
  @suite_option_requirements
end

Class Method Details

.extended(extending_class) ⇒ Object

When a class (e.g. TestSuite/TestGroup) uses this module, set it up so that subclassing it works correctly.

  • add the subclass to the relevant repository when it is created

  • copy the class instance variables from the superclass

  • add a hook to the subclass so that its subclasses do the same



23
24
25
26
27
28
29
30
31
# File 'lib/inferno/dsl/runnable.rb', line 23

def self.extended(extending_class)
  super
  extending_class.extend Configurable
  extending_class.extend InputOutputHandling

  extending_class.define_singleton_method(:inherited) do |subclass|
    copy_instance_variables(subclass)
  end
end

Instance Method Details

#add_self_to_repositoryObject



68
69
70
# File 'lib/inferno/dsl/runnable.rb', line 68

def add_self_to_repository
  repository.insert(self)
end

#all_childrenObject



312
313
314
# File 'lib/inferno/dsl/runnable.rb', line 312

def all_children
  @all_children ||= []
end

#block(&block) ⇒ Proc Also known as: run

Set/Get the block that is executed when a runnable is run

Parameters:

  • block (Proc)

Returns:

  • (Proc)

    the block that is executed when a runnable is run



303
304
305
306
307
# File 'lib/inferno/dsl/runnable.rb', line 303

def block(&block)
  return @block unless block_given?

  @block = block
end

#child_metadata(metadata = nil) ⇒ Object



117
118
119
120
# File 'lib/inferno/dsl/runnable.rb', line 117

def ( = nil)
  @child_metadata =  if 
  @child_metadata
end

#children(selected_suite_options = []) ⇒ Object



456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/inferno/dsl/runnable.rb', line 456

def children(selected_suite_options = [])
  return all_children if selected_suite_options.blank?

  all_children.select do |child|
    requirements = child.suite_option_requirements

    if requirements.blank?
      true
    else
      requirements.all? { |requirement| selected_suite_options.include? requirement }
    end
  end
end

#configure_child_class(klass, hash_args) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/inferno/dsl/runnable.rb', line 136

def configure_child_class(klass, hash_args) # rubocop:disable Metrics/CyclomaticComplexity
  inputs.each do |name|
    next if klass.inputs.any? { |klass_input_name| klass_input_name == name }

    klass.input name
  end

  outputs.each do |output_name|
    next if klass.outputs.include? output_name

    klass.output output_name
  end

  new_fhir_client_definitions = klass.instance_variable_get(:@fhir_client_definitions) || {}
  fhir_client_definitions.each do |name, definition|
    next if new_fhir_client_definitions.include? name

    new_fhir_client_definitions[name] = definition.dup
  end
  klass.instance_variable_set(:@fhir_client_definitions, new_fhir_client_definitions)

  new_http_client_definitions = klass.instance_variable_get(:@http_client_definitions) || {}
  http_client_definitions.each do |name, definition|
    next if new_http_client_definitions.include? name

    new_http_client_definitions[name] = definition.dup
  end
  klass.instance_variable_set(:@http_client_definitions, new_http_client_definitions)

  klass.config(config)

  klass.all_children.select!(&:required?) if hash_args.delete(:exclude_optional)

  hash_args.each do |key, value|
    if value.is_a? Array
      klass.send(key, *value)
    else
      klass.send(key, value)
    end
  end

  klass.all_children.each do |child_class|
    klass.configure_child_class(child_class, {})
    child_class.add_self_to_repository
  end
end

#copy_instance_variables(subclass) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/inferno/dsl/runnable.rb', line 51

def copy_instance_variables(subclass)
  instance_variables
    .reject { |variable| VARIABLES_NOT_TO_COPY.include? variable }
    .each { |variable| subclass.instance_variable_set(variable, instance_variable_get(variable).dup) }

  subclass.config(config)

  new_children = all_children.map do |child|
    Class.new(child).tap do |subclass_child|
      subclass_child.parent = subclass
    end
  end

  subclass.instance_variable_set(:@all_children, new_children)
end

#create_child_class(hash_args) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/inferno/dsl/runnable.rb', line 123

def create_child_class(hash_args)
  superclass_id = hash_args.delete :from

  return Class.new([:class]) if superclass_id.blank?

  superclass = [:repo].find(superclass_id)

  raise Exceptions::ParentNotLoadedException.new([:class], superclass_id) unless superclass

  Class.new(superclass)
end

#default_idObject



295
296
297
# File 'lib/inferno/dsl/runnable.rb', line 295

def default_id
  to_s
end

#define_child(*args) ⇒ Object

This method defines a child entity. Classes using this module should alias the method name they wish to use to define child entities to this method.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/inferno/dsl/runnable.rb', line 82

def define_child(*args, &)
  hash_args = process_args(args)

  klass = create_child_class(hash_args)

  klass.parent = self

  all_children << klass

  configure_child_class(klass, hash_args)

  handle_child_definition_block(klass, &)

  klass.add_self_to_repository

  klass
end

#description(new_description = nil) ⇒ String

Set/Get a runnable’s description

Parameters:

  • new_description (String) (defaults to: nil)

Returns:

  • (String)

    the description



235
236
237
238
239
# File 'lib/inferno/dsl/runnable.rb', line 235

def description(new_description = nil)
  return @description if new_description.nil?

  @description = format_markdown(new_description)
end

#handle_child_definition_block(klass) ⇒ Object



184
185
186
# File 'lib/inferno/dsl/runnable.rb', line 184

def handle_child_definition_block(klass, &)
  klass.class_eval(&) if block_given?
end

#id(new_id = nil) ⇒ String, Symbol

Set/Get a runnable’s id

Parameters:

  • new_id (String, Symbol) (defaults to: nil)

Returns:

  • (String, Symbol)

    the id

Raises:



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/inferno/dsl/runnable.rb', line 192

def id(new_id = nil)
  return @id if new_id.nil? && @id.present?

  prefix =
    if parent
      "#{parent.id}-"
    else
      ''
    end

  @base_id = new_id || @base_id || default_id

  final_id = "#{prefix}#{@base_id}"

  raise Exceptions::InvalidRunnableIdException, final_id if final_id.length > 255

  @id = final_id
end

#input_instructions(new_input_instructions = nil) ⇒ String

Set/Get a runnable’s input instructions

Parameters:

  • new_input_instructions (String) (defaults to: nil)

Returns:

  • (String)

    the input instructions



255
256
257
258
259
# File 'lib/inferno/dsl/runnable.rb', line 255

def input_instructions(new_input_instructions = nil)
  return @input_instructions if new_input_instructions.nil?

  @input_instructions = format_markdown(new_input_instructions)
end

#inspectObject



471
472
473
474
475
476
477
478
479
# File 'lib/inferno/dsl/runnable.rb', line 471

def inspect
  non_dynamic_ancestor = ancestors.find { |ancestor| !ancestor.to_s.start_with? '#' }
  "#<#{non_dynamic_ancestor}".tap do |inspect_string|
    inspect_string.concat(" @id=#{id.inspect},")
    inspect_string.concat(" @short_id=#{short_id.inspect},") if respond_to? :short_id
    inspect_string.concat(" @title=#{title.inspect}")
    inspect_string.concat('>')
  end
end

#optional(optional = true) ⇒ void

This method returns an undefined value.

Mark as optional. Tests are required by default.

Parameters:

  • optional (Boolean) (defaults to: true)


265
266
267
# File 'lib/inferno/dsl/runnable.rb', line 265

def optional(optional = true) # rubocop:disable Style/OptionalBooleanParameter
  @optional = optional
end

#optional?Boolean

The test or group is optional if true

Returns:

  • (Boolean)


283
284
285
# File 'lib/inferno/dsl/runnable.rb', line 283

def optional?
  !!@optional
end

#process_args(args) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/inferno/dsl/runnable.rb', line 101

def process_args(args)
  hash_args =
    if args[0].is_a? Hash
      args[0]
    elsif args[1].is_a? Hash
      args[1]
    else
      {}
    end

  hash_args[:title] = args[0] if args[0].is_a? String

  hash_args
end

#repositoryObject

An instance of the repository for the class using this module



74
75
76
# File 'lib/inferno/dsl/runnable.rb', line 74

def repository
  nil
end

#required(required = true) ⇒ void

This method returns an undefined value.

Mark as required

Tests are required by default. This method is provided to make an existing optional test required.

Parameters:

  • required (Boolean) (defaults to: true)


276
277
278
# File 'lib/inferno/dsl/runnable.rb', line 276

def required(required = true) # rubocop:disable Style/OptionalBooleanParameter
  @optional = !required
end

#required?Boolean

The test or group is required if true

Returns:

  • (Boolean)


290
291
292
# File 'lib/inferno/dsl/runnable.rb', line 290

def required?
  !optional?
end

#required_suite_options(suite_option_requirements) ⇒ void

This method returns an undefined value.

Set/get suite options required for this runnable to be executed.

Examples:

suite_option :ig_version,
            list_options: [
              {
                label: 'IG v1',
                value: 'ig_v1'
              },
              {
                label: 'IG v2',
                value: 'ig_v2'
              }
            ]

group from: :ig_v1_group,
      required_suite_options: { ig_version: 'ig_v1' }

group from: :ig_v2_group do
  required_suite_options ig_version: 'ig_v2'
end

Parameters:

  • suite_option_requirements (Hash)


448
449
450
451
452
453
# File 'lib/inferno/dsl/runnable.rb', line 448

def required_suite_options(suite_option_requirements)
  @suite_option_requirements =
    suite_option_requirements.map do |key, value|
      DSL::SuiteOption.new(id: key, value:)
    end
end

#resume_test_route(method, path, tags: [], result: 'pass') { ... } ⇒ void

This method returns an undefined value.

Create a route which will resume a test run when a request is received

Examples:

resume_test_route :get, '/launch', tags: ['launch'] do
  request.query_parameters['iss']
end

test do
  input :issuer
  receives_request :launch

  run do
    wait(
      identifier: issuer,
      message: "Wating to receive a request with an issuer of #{issuer}"
    )
  end
end

Parameters:

  • method (Symbol)

    the HTTP request type (:get, :post, etc.) for the incoming request

  • path (String)

    the path for this request. The route will be served with a prefix of ‘/custom/TEST_SUITE_ID` to prevent path conflicts. [Any of the path options available in Hanami Router](github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#a-beautiful-dsl) can be used here.

  • tags (Array<String>) (defaults to: [])

    a list of tags to assign to the request

  • result (String) (defaults to: 'pass')

    the result for the waiting test. Must be one of: ‘pass’, ‘fail’, ‘skip’, ‘omit’, ‘cancel’

Yields:

  • This method takes a block which must return the identifier defined when a test was set to wait for the test run that hit this route. The block has access to the ‘request` method which returns a Entities::Request object with the information for the incoming request.

See Also:



359
360
361
362
363
364
365
366
367
# File 'lib/inferno/dsl/runnable.rb', line 359

def resume_test_route(method, path, tags: [], result: 'pass', &block)
  route_class = Class.new(ResumeTestRoute) do |klass|
    klass.singleton_class.instance_variable_set(:@test_run_identifier_block, block)
    klass.singleton_class.instance_variable_set(:@tags, tags)
    klass.singleton_class.instance_variable_set(:@result, result)
  end

  route(method, path, route_class)
end

#route(method, path, handler) ⇒ void

This method returns an undefined value.

Create a route to handle a request

Parameters:



401
402
403
# File 'lib/inferno/dsl/runnable.rb', line 401

def route(method, path, handler)
  Inferno.routes << { method:, path:, handler:, suite: }
end

#short_description(new_short_description = nil) ⇒ String

Set/Get a runnable’s short one-sentence description

Parameters:

  • new_short_description (String) (defaults to: nil)

Returns:

  • (String)

    the one-sentence description



245
246
247
248
249
# File 'lib/inferno/dsl/runnable.rb', line 245

def short_description(new_short_description = nil)
  return @short_description if new_short_description.nil?

  @short_description = format_markdown(new_short_description)
end

#short_title(new_short_title = nil) ⇒ String

Set/Get a runnable’s short title

Parameters:

  • new_short_title (String) (defaults to: nil)

Returns:

  • (String)

    the short title



225
226
227
228
229
# File 'lib/inferno/dsl/runnable.rb', line 225

def short_title(new_short_title = nil)
  return @short_title if new_short_title.nil?

  @short_title = new_short_title
end

#suiteObject



317
318
319
320
321
# File 'lib/inferno/dsl/runnable.rb', line 317

def suite
  return self if ancestors.include? Inferno::Entities::TestSuite

  parent.suite
end

#suite_endpoint(method, path, endpoint_class) ⇒ void

This method returns an undefined value.

Create an endpoint to receive incoming requests during a Test Run.

Examples:

suite_endpoint :post, '/my_suite_endpoint', MySuiteEndpoint

Parameters:

  • method (Symbol)

    the HTTP request type (:get, :post, etc.) for the incoming request

  • path (String)

    the path for this request. The route will be served with a prefix of ‘/custom/TEST_SUITE_ID` to prevent path conflicts. [Any of the path options available in Hanami Router](github.com/hanami/router/tree/f41001d4c3ee9e2d2c7bb142f74b43f8e1d3a265#a-beautiful-dsl) can be used here.

  • a (Class)

    subclass of Inferno::DSL::SuiteEndpoint

See Also:



383
384
385
# File 'lib/inferno/dsl/runnable.rb', line 383

def suite_endpoint(method, path, endpoint_class)
  route(method, path, endpoint_class)
end

#test_count(selected_suite_options = []) ⇒ Object



406
407
408
409
410
411
412
413
414
415
416
# File 'lib/inferno/dsl/runnable.rb', line 406

def test_count(selected_suite_options = [])
  @test_counts ||= {}

  options_json = selected_suite_options.to_json

  return @test_counts[options_json] if @test_counts[options_json]

  @test_counts[options_json] =
    children(selected_suite_options)
      &.reduce(0) { |sum, child| sum + child.test_count(selected_suite_options) } || 0
end

#title(new_title = nil) ⇒ String

Set/Get a runnable’s title

Parameters:

  • new_title (String) (defaults to: nil)

Returns:

  • (String)

    the title



215
216
217
218
219
# File 'lib/inferno/dsl/runnable.rb', line 215

def title(new_title = nil)
  return @title if new_title.nil?

  @title = new_title
end

#user_runnable?Boolean

Returns:

  • (Boolean)


419
420
421
422
423
# File 'lib/inferno/dsl/runnable.rb', line 419

def user_runnable?
  @user_runnable ||= parent.nil? ||
                     !parent.respond_to?(:run_as_group?) ||
                     (parent.user_runnable? && !parent.run_as_group?)
end