inferno_core 0.0.3 → 0.0.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno.rb +4 -0
  3. data/lib/inferno/apps/web/controllers/test_runs/create.rb +17 -10
  4. data/lib/inferno/apps/web/controllers/test_runs/show.rb +10 -0
  5. data/lib/inferno/apps/web/controllers/test_sessions/create.rb +1 -0
  6. data/lib/inferno/apps/web/controllers/test_sessions/last_test_run.rb +22 -0
  7. data/lib/inferno/apps/web/controllers/test_sessions/results/index.rb +6 -1
  8. data/lib/inferno/apps/web/controllers/test_sessions/session_data/index.rb +21 -0
  9. data/lib/inferno/apps/web/router.rb +15 -0
  10. data/lib/inferno/apps/web/serializers/request.rb +1 -0
  11. data/lib/inferno/apps/web/serializers/result.rb +8 -0
  12. data/lib/inferno/apps/web/serializers/session_data.rb +10 -0
  13. data/lib/inferno/apps/web/serializers/test.rb +1 -3
  14. data/lib/inferno/apps/web/serializers/test_group.rb +2 -3
  15. data/lib/inferno/apps/web/serializers/test_run.rb +1 -0
  16. data/lib/inferno/apps/web/serializers/test_session.rb +1 -1
  17. data/lib/inferno/apps/web/serializers/test_suite.rb +1 -0
  18. data/lib/inferno/config/application.rb +4 -2
  19. data/lib/inferno/config/boot.rb +2 -0
  20. data/lib/inferno/config/boot/db.rb +10 -1
  21. data/lib/inferno/config/boot/sidekiq.rb +11 -0
  22. data/lib/inferno/db/migrations/001_create_initial_structure.rb +0 -21
  23. data/lib/inferno/db/migrations/002_add_wait_support.rb +7 -0
  24. data/lib/inferno/db/migrations/003_update_session_data.rb +18 -0
  25. data/lib/inferno/db/migrations/004_add_request_results_table.rb +9 -0
  26. data/lib/inferno/db/migrations/005_add_updated_at_index_to_results.rb +5 -0
  27. data/lib/inferno/db/schema.rb +154 -0
  28. data/lib/inferno/dsl.rb +1 -3
  29. data/lib/inferno/dsl/fhir_client_builder.rb +16 -0
  30. data/lib/inferno/dsl/request_storage.rb +12 -0
  31. data/lib/inferno/dsl/results.rb +49 -0
  32. data/lib/inferno/dsl/resume_test_route.rb +89 -0
  33. data/lib/inferno/dsl/runnable.rb +96 -7
  34. data/lib/inferno/entities.rb +1 -1
  35. data/lib/inferno/entities/header.rb +7 -7
  36. data/lib/inferno/entities/message.rb +8 -6
  37. data/lib/inferno/entities/request.rb +40 -14
  38. data/lib/inferno/entities/result.rb +34 -18
  39. data/lib/inferno/entities/session_data.rb +33 -0
  40. data/lib/inferno/entities/test.rb +20 -7
  41. data/lib/inferno/entities/test_run.rb +13 -6
  42. data/lib/inferno/entities/test_session.rb +8 -8
  43. data/lib/inferno/exceptions.rb +12 -0
  44. data/lib/inferno/jobs.rb +16 -0
  45. data/lib/inferno/jobs/execute_test_run.rb +14 -0
  46. data/lib/inferno/jobs/resume_test_run.rb +14 -0
  47. data/lib/inferno/public/bundle.js +1 -1
  48. data/lib/inferno/repositories/repository.rb +13 -0
  49. data/lib/inferno/repositories/requests.rb +5 -4
  50. data/lib/inferno/repositories/results.rb +151 -3
  51. data/lib/inferno/repositories/session_data.rb +47 -0
  52. data/lib/inferno/repositories/test_runs.rb +66 -0
  53. data/lib/inferno/test_runner.rb +121 -29
  54. data/lib/inferno/utils/middleware/request_logger.rb +16 -3
  55. data/lib/inferno/version.rb +1 -1
  56. data/spec/factories/result.rb +8 -0
  57. data/spec/factories/test_run.rb +2 -0
  58. metadata +32 -5
  59. data/lib/inferno/dsl/fhir_manipulation.rb +0 -25
  60. data/lib/inferno/entities/test_input.rb +0 -20
@@ -0,0 +1,47 @@
1
+ module Inferno
2
+ module Repositories
3
+ class SessionData < Repository
4
+ def save(params)
5
+ name = params[:name].to_s.downcase
6
+ test_session_id = params[:test_session_id]
7
+ db
8
+ .insert_conflict(
9
+ target: :id,
10
+ update: { value: params[:value] }
11
+ ).insert(
12
+ id: "#{test_session_id}_#{name}",
13
+ name: name,
14
+ value: params[:value],
15
+ test_session_id: test_session_id
16
+ )
17
+ end
18
+
19
+ def load(test_session_id:, name:)
20
+ self.class::Model
21
+ .find(test_session_id: test_session_id, name: name.to_s.downcase)
22
+ &.value
23
+ end
24
+
25
+ def get_all_from_session(test_session_id)
26
+ self.class::Model
27
+ .where(test_session_id: test_session_id)
28
+ .all
29
+ .map! do |session_data_hash|
30
+ build_entity(
31
+ session_data_hash
32
+ .to_json_data
33
+ .deep_symbolize_keys!
34
+ )
35
+ end
36
+ end
37
+
38
+ def entity_class_name
39
+ 'SessionData'
40
+ end
41
+
42
+ class Model < Sequel::Model(db)
43
+ many_to_one :test_session, class: 'Inferno::Repositories::TestSessions::Model', key: :test_session_id
44
+ end
45
+ end
46
+ end
47
+ end
@@ -24,6 +24,65 @@ module Inferno
24
24
  .map! { |result| results_repo.build_entity(result) }
25
25
  end
26
26
 
27
+ def find_latest_waiting_by_identifier(identifier)
28
+ test_run_hash =
29
+ self.class::Model
30
+ .where(status: 'waiting')
31
+ .where(identifier: identifier)
32
+ .where { wait_timeout >= Time.now }
33
+ .order(Sequel.desc(:updated_at))
34
+ .limit(1)
35
+ .to_a
36
+ &.first
37
+ &.to_hash
38
+
39
+ return nil if test_run_hash.nil?
40
+
41
+ build_entity(test_run_hash)
42
+ end
43
+
44
+ def last_test_run(test_session_id)
45
+ test_run_hash =
46
+ self.class::Model
47
+ .where(test_session_id: test_session_id)
48
+ .order(Sequel.desc(:updated_at))
49
+ .limit(1)
50
+ .to_a
51
+ .map { |record| record.to_json_data(json_serializer_options).deep_symbolize_keys! }
52
+ &.first
53
+ &.to_hash
54
+
55
+ return nil if test_run_hash.nil?
56
+
57
+ build_entity(test_run_hash)
58
+ end
59
+
60
+ def mark_as_running(test_run_id)
61
+ update(test_run_id, status: 'running')
62
+ end
63
+
64
+ def mark_as_done(test_run_id)
65
+ update(test_run_id, status: 'done')
66
+ end
67
+
68
+ def mark_as_waiting(test_run_id, identifier, timeout)
69
+ update(
70
+ test_run_id,
71
+ status: 'waiting',
72
+ identifier: identifier,
73
+ wait_timeout: Time.now + timeout.seconds
74
+ )
75
+ end
76
+
77
+ def mark_as_no_longer_waiting(test_run_id)
78
+ update(
79
+ test_run_id,
80
+ status: 'queued',
81
+ identifier: nil,
82
+ wait_timeout: nil
83
+ )
84
+ end
85
+
27
86
  class Model < Sequel::Model(db)
28
87
  include ValidateRunnableReference
29
88
 
@@ -33,6 +92,13 @@ module Inferno
33
92
  key: :test_run_id
34
93
  many_to_one :test_session, class: 'Inferno::Repositories::TestSessions::Model', key: :test_session_id
35
94
 
95
+ def validate
96
+ super
97
+ if status.present? && !Entities::TestRun::STATUS_OPTIONS.include?(status) # rubocop:disable Style/GuardClause
98
+ errors.add(:status, "'#{status}' is not valid")
99
+ end
100
+ end
101
+
36
102
  def before_create
37
103
  self.id = SecureRandom.uuid
38
104
  time = Time.now
@@ -1,73 +1,165 @@
1
1
  module Inferno
2
2
  # @api private
3
3
  class TestRunner
4
- attr_reader :test_session, :test_run
4
+ attr_reader :test_session, :test_run, :resuming
5
5
 
6
- def initialize(test_session:, test_run:)
6
+ def initialize(test_session:, test_run:, resume: false)
7
7
  @test_session = test_session
8
8
  @test_run = test_run
9
+ @resuming = resume
10
+ end
11
+
12
+ def run_results
13
+ @run_results ||= {}
9
14
  end
10
15
 
11
16
  def results_repo
12
17
  @results_repo ||= Repositories::Results.new
13
18
  end
14
19
 
15
- def run(runnable, inputs = {}, outputs = {})
20
+ def test_runs_repo
21
+ @test_runs_repo ||= Repositories::TestRuns.new
22
+ end
23
+
24
+ def session_data_repo
25
+ @session_data_repo ||= Repositories::SessionData.new
26
+ end
27
+
28
+ def start
29
+ test_runs_repo.mark_as_running(test_run.id)
30
+
31
+ run(test_run.runnable)
32
+
33
+ test_runs_repo.mark_as_done(test_run.id) unless run_results.values.any?(&:waiting?)
34
+
35
+ run_results.values
36
+ end
37
+
38
+ def run(runnable)
16
39
  if runnable < Entities::Test
17
- run_test(runnable, inputs, outputs)
40
+ return existing_test_result(runnable) || run_test(runnable) if resuming
41
+
42
+ run_test(runnable)
18
43
  else
19
- run_group(runnable, inputs, outputs)
44
+ run_group(runnable)
20
45
  end
21
46
  end
22
47
 
23
- def run_test(runnable, inputs = {}, outputs = {})
24
- test_instance = runnable.new(inputs: inputs.merge(outputs), test_session_id: test_session.id)
48
+ def existing_test_result(runnable)
49
+ results_repo.result_for_test_run(runnable.reference_hash.merge(test_run_id: test_run.id))
50
+ end
51
+
52
+ def run_test(test)
53
+ inputs = load_inputs(test)
54
+
55
+ input_json_string = JSON.generate(inputs)
56
+ test_instance = test.new(inputs: inputs, test_session_id: test_session.id)
25
57
 
26
58
  result = begin
27
- inputs.merge(outputs).each do |key, value|
28
- test_instance.instance_variable_set("@#{key}", value)
29
- end
30
59
  test_instance.load_named_requests
31
- test_instance.instance_eval(&runnable.block)
60
+ test_instance.instance_eval(&test.block)
32
61
  'pass'
33
62
  rescue Exceptions::TestResultException => e
34
63
  test_instance.result_message = e.message
35
64
  e.result
36
65
  rescue StandardError => e
66
+ Application['logger'].error(e.full_message)
37
67
  test_instance.result_message = "Error: #{e.message}"
38
68
  'error'
39
69
  end
40
70
 
41
- runnable.outputs.each do |output|
42
- outputs[output] = test_instance.send(output)
71
+ outputs = save_outputs(test_instance)
72
+ output_json_string = JSON.generate(outputs)
73
+
74
+ if result == 'wait'
75
+ test_runs_repo.mark_as_waiting(test_run.id, test_instance.identifier, test_instance.wait_timeout)
43
76
  end
44
77
 
45
- [persist_result(
78
+ test_result = persist_result(
46
79
  {
47
- test_session_id: test_session.id,
48
- test_run_id: test_run.id,
49
80
  messages: test_instance.messages,
50
81
  requests: test_instance.requests,
51
82
  result: result,
52
- result_message: test_instance.result_message
53
- }.merge(runnable.reference_hash)
54
- )]
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
+
89
+ # If running a single test, update its parents' results. If running a
90
+ # group or suite, #run_group handles updating the parents.
91
+ return test_result if test_run.test_id.blank?
92
+
93
+ update_parent_result(test.parent)
94
+
95
+ test_result
55
96
  end
56
97
 
57
- def run_group(runnable, inputs = {}, outputs = {})
58
- results = runnable.children.flat_map { |child| run(child, inputs, outputs) }
98
+ def run_group(group)
99
+ results = []
100
+ group.children.each do |child|
101
+ result = run(child)
102
+ results << result
103
+ break if results.last.waiting?
104
+ end
59
105
 
60
- results << persist_result(
61
- {
62
- test_session_id: test_session.id,
63
- test_run_id: test_run.id,
64
- result: roll_up_result(results)
65
- }.merge(runnable.reference_hash)
66
- )
106
+ results.flatten!
107
+
108
+ group_result = persist_result(group.reference_hash.merge(result: roll_up_result(results)))
109
+
110
+ update_parent_result(group.parent)
111
+
112
+ group_result
113
+ end
114
+
115
+ def update_parent_result(parent)
116
+ return if parent.nil?
117
+
118
+ children = parent.children
119
+ child_results = results_repo.current_results_for_test_session_and_runnables(test_session.id, children)
120
+ return if children.length != child_results.length
121
+
122
+ old_result = results_repo.current_result_for_test_session(test_session.id, parent.reference_hash)&.result
123
+ new_result = roll_up_result(child_results)
124
+
125
+ if new_result != old_result
126
+ persist_result(parent.reference_hash.merge(result: new_result))
127
+
128
+ update_parent_result(parent.parent)
129
+ end
130
+
131
+ new_result
132
+ end
133
+
134
+ def load_inputs(runnable)
135
+ runnable.inputs.each_with_object({}) do |input, input_hash|
136
+ name = input[:name]
137
+ input_hash[name] = session_data_repo.load(test_session_id: test_session.id, name: name)
138
+ end
139
+ end
140
+
141
+ def save_outputs(runnable_instance)
142
+ outputs =
143
+ runnable_instance.outputs.map do |output_name|
144
+ {
145
+ name: output_name,
146
+ value: runnable_instance.send(output_name)
147
+ }
148
+ end
149
+ outputs.compact!
150
+ outputs.each do |output|
151
+ session_data_repo.save(output.merge(test_session_id: test_session.id))
152
+ end
153
+
154
+ outputs
67
155
  end
68
156
 
69
157
  def persist_result(params)
70
- results_repo.create(params)
158
+ result = results_repo.create(
159
+ params.merge(test_run_id: test_run.id, test_session_id: test_session.id)
160
+ )
161
+
162
+ run_results[result.runnable.id] = result
71
163
  end
72
164
 
73
165
  def roll_up_result(results)
@@ -29,11 +29,17 @@ module Inferno
29
29
 
30
30
  def log_response(response, start_time, end_time, exception = nil)
31
31
  elapsed = end_time - start_time
32
- status, _response_headers, response_body = response if response
32
+ status, _response_headers, body = response if response
33
33
  status, = response if exception
34
34
 
35
35
  logger.info("#{status} in #{elapsed.in_milliseconds} ms")
36
- logger.info(response_body)
36
+ return unless body.present?
37
+
38
+ if body.length > 100
39
+ logger.info("#{body[0..100]}...")
40
+ else
41
+ logger.info(body)
42
+ end
37
43
  end
38
44
 
39
45
  def log_request(env)
@@ -47,7 +53,14 @@ module Inferno
47
53
  query_string = query.blank? ? '' : "?#{query}"
48
54
 
49
55
  logger.info("#{method} #{scheme}://#{host}#{path}#{query_string}")
50
- logger.info(body) if body.present?
56
+
57
+ return unless body.present?
58
+
59
+ if body.length > 100
60
+ logger.info("#{body[0..100]}...")
61
+ else
62
+ logger.info(body)
63
+ end
51
64
  end
52
65
  end
53
66
  end
@@ -1,3 +1,3 @@
1
1
  module Inferno
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
@@ -14,11 +14,19 @@ FactoryBot.define do
14
14
  test_suite_id { runnable[:test_suite_id] }
15
15
  test_group_id { runnable[:test_group_id] }
16
16
  test_id { runnable[:test_id] }
17
+ output_json { '[]' }
17
18
 
18
19
  result { 'pass' }
19
20
 
20
21
  initialize_with { new(**attributes) }
21
22
 
23
+ before(:create) do |instance, evaluator|
24
+ instance.instance_variable_set(
25
+ :@requests,
26
+ repo_create_list(:request, evaluator.request_count, result_id: instance.id)
27
+ )
28
+ end
29
+
22
30
  to_create do |instance|
23
31
  Inferno::Repositories::Results.new.create(instance.to_hash)
24
32
  end
@@ -10,8 +10,10 @@ FactoryBot.define do
10
10
  test_suite_id { runnable[:test_suite_id] }
11
11
  test_group_id { runnable[:test_group_id] }
12
12
  test_id { runnable[:test_id] }
13
+ status { 'queued' }
13
14
 
14
15
  inputs { nil }
16
+ wait_timeout { nil }
15
17
 
16
18
  initialize_with { new(**attributes) }
17
19
 
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: 0.0.3
4
+ version: 0.0.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: 2021-06-21 00:00:00.000000000 Z
13
+ date: 2021-08-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -96,6 +96,20 @@ dependencies:
96
96
  - - "~>"
97
97
  - !ruby/object:Gem::Version
98
98
  version: '4.0'
99
+ - !ruby/object:Gem::Dependency
100
+ name: fhir_models
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 4.2.0
106
+ type: :runtime
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: 4.2.0
99
113
  - !ruby/object:Gem::Dependency
100
114
  name: hanami-controller
101
115
  requirement: !ruby/object:Gem::Requirement
@@ -409,7 +423,9 @@ files:
409
423
  - lib/inferno/apps/web/controllers/test_runs/results/index.rb
410
424
  - lib/inferno/apps/web/controllers/test_runs/show.rb
411
425
  - lib/inferno/apps/web/controllers/test_sessions/create.rb
426
+ - lib/inferno/apps/web/controllers/test_sessions/last_test_run.rb
412
427
  - lib/inferno/apps/web/controllers/test_sessions/results/index.rb
428
+ - lib/inferno/apps/web/controllers/test_sessions/session_data/index.rb
413
429
  - lib/inferno/apps/web/controllers/test_sessions/show.rb
414
430
  - lib/inferno/apps/web/controllers/test_suites/index.rb
415
431
  - lib/inferno/apps/web/controllers/test_suites/show.rb
@@ -421,6 +437,7 @@ files:
421
437
  - lib/inferno/apps/web/serializers/request.rb
422
438
  - lib/inferno/apps/web/serializers/result.rb
423
439
  - lib/inferno/apps/web/serializers/serializer.rb
440
+ - lib/inferno/apps/web/serializers/session_data.rb
424
441
  - lib/inferno/apps/web/serializers/test.rb
425
442
  - lib/inferno/apps/web/serializers/test_group.rb
426
443
  - lib/inferno/apps/web/serializers/test_run.rb
@@ -430,19 +447,25 @@ files:
430
447
  - lib/inferno/config/boot.rb
431
448
  - lib/inferno/config/boot/db.rb
432
449
  - lib/inferno/config/boot/logging.rb
450
+ - lib/inferno/config/boot/sidekiq.rb
433
451
  - lib/inferno/config/boot/suites.rb
434
452
  - lib/inferno/config/boot/web.rb
435
453
  - lib/inferno/db/migrations/001_create_initial_structure.rb
454
+ - lib/inferno/db/migrations/002_add_wait_support.rb
455
+ - lib/inferno/db/migrations/003_update_session_data.rb
456
+ - lib/inferno/db/migrations/004_add_request_results_table.rb
457
+ - lib/inferno/db/migrations/005_add_updated_at_index_to_results.rb
458
+ - lib/inferno/db/schema.rb
436
459
  - lib/inferno/dsl.rb
437
460
  - lib/inferno/dsl/assertions.rb
438
461
  - lib/inferno/dsl/fhir_client.rb
439
462
  - lib/inferno/dsl/fhir_client_builder.rb
440
- - lib/inferno/dsl/fhir_manipulation.rb
441
463
  - lib/inferno/dsl/fhir_validation.rb
442
464
  - lib/inferno/dsl/http_client.rb
443
465
  - lib/inferno/dsl/http_client_builder.rb
444
466
  - lib/inferno/dsl/request_storage.rb
445
467
  - lib/inferno/dsl/results.rb
468
+ - lib/inferno/dsl/resume_test_route.rb
446
469
  - lib/inferno/dsl/runnable.rb
447
470
  - lib/inferno/entities.rb
448
471
  - lib/inferno/entities/attributes.rb
@@ -451,13 +474,16 @@ files:
451
474
  - lib/inferno/entities/message.rb
452
475
  - lib/inferno/entities/request.rb
453
476
  - lib/inferno/entities/result.rb
477
+ - lib/inferno/entities/session_data.rb
454
478
  - lib/inferno/entities/test.rb
455
479
  - lib/inferno/entities/test_group.rb
456
- - lib/inferno/entities/test_input.rb
457
480
  - lib/inferno/entities/test_run.rb
458
481
  - lib/inferno/entities/test_session.rb
459
482
  - lib/inferno/entities/test_suite.rb
460
483
  - lib/inferno/exceptions.rb
484
+ - lib/inferno/jobs.rb
485
+ - lib/inferno/jobs/execute_test_run.rb
486
+ - lib/inferno/jobs/resume_test_run.rb
461
487
  - lib/inferno/public/217.bundle.js
462
488
  - lib/inferno/public/assets.json
463
489
  - lib/inferno/public/bundle.js
@@ -473,6 +499,7 @@ files:
473
499
  - lib/inferno/repositories/repository.rb
474
500
  - lib/inferno/repositories/requests.rb
475
501
  - lib/inferno/repositories/results.rb
502
+ - lib/inferno/repositories/session_data.rb
476
503
  - lib/inferno/repositories/test_groups.rb
477
504
  - lib/inferno/repositories/test_runs.rb
478
505
  - lib/inferno/repositories/test_sessions.rb
@@ -515,7 +542,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
515
542
  - !ruby/object:Gem::Version
516
543
  version: '0'
517
544
  requirements: []
518
- rubygems_version: 3.1.4
545
+ rubygems_version: 3.1.6
519
546
  signing_key:
520
547
  specification_version: 4
521
548
  summary: Inferno Core is an open source tool for testing data exchanges enabled by