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.



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

def parent
  @parent
end

#suite_option_requirementsObject (readonly)

Returns the value of attribute suite_option_requirements.



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

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



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

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



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

def add_self_to_repository
  repository.insert(self)
end

#all_childrenObject



307
308
309
# File 'lib/inferno/dsl/runnable.rb', line 307

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



298
299
300
301
302
# File 'lib/inferno/dsl/runnable.rb', line 298

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

  @block = block
end

#child_metadata(metadata = nil) ⇒ Object



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

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

#children(selected_suite_options = []) ⇒ Object



451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'lib/inferno/dsl/runnable.rb', line 451

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



135
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
# File 'lib/inferno/dsl/runnable.rb', line 135

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



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

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



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

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



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

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.



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

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



230
231
232
233
234
# File 'lib/inferno/dsl/runnable.rb', line 230

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

  @description = format_markdown(new_description)
end

#handle_child_definition_block(klass) ⇒ Object



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

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



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

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

  @id = "#{prefix}#{@base_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



250
251
252
253
254
# File 'lib/inferno/dsl/runnable.rb', line 250

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

  @input_instructions = format_markdown(new_input_instructions)
end

#inspectObject



466
467
468
469
470
471
472
473
474
# File 'lib/inferno/dsl/runnable.rb', line 466

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)


260
261
262
# File 'lib/inferno/dsl/runnable.rb', line 260

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

#optional?Boolean

The test or group is optional if true

Returns:

  • (Boolean)


278
279
280
# File 'lib/inferno/dsl/runnable.rb', line 278

def optional?
  !!@optional
end

#process_args(args) ⇒ Object



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

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



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

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)


271
272
273
# File 'lib/inferno/dsl/runnable.rb', line 271

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

#required?Boolean

The test or group is required if true

Returns:

  • (Boolean)


285
286
287
# File 'lib/inferno/dsl/runnable.rb', line 285

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)


443
444
445
446
447
448
# File 'lib/inferno/dsl/runnable.rb', line 443

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:



354
355
356
357
358
359
360
361
362
# File 'lib/inferno/dsl/runnable.rb', line 354

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:



396
397
398
# File 'lib/inferno/dsl/runnable.rb', line 396

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



240
241
242
243
244
# File 'lib/inferno/dsl/runnable.rb', line 240

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



220
221
222
223
224
# File 'lib/inferno/dsl/runnable.rb', line 220

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

  @short_title = new_short_title
end

#suiteObject



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

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:



378
379
380
# File 'lib/inferno/dsl/runnable.rb', line 378

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

#test_count(selected_suite_options = []) ⇒ Object



401
402
403
404
405
406
407
408
409
410
411
# File 'lib/inferno/dsl/runnable.rb', line 401

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



210
211
212
213
214
# File 'lib/inferno/dsl/runnable.rb', line 210

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

  @title = new_title
end

#user_runnable?Boolean

Returns:

  • (Boolean)


414
415
416
417
418
# File 'lib/inferno/dsl/runnable.rb', line 414

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