inferno_core 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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