-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FI-2937 inferno execute #517
Changes from 88 commits
f9021af
7d8b8d5
5a72514
625400d
72fb726
4588c3f
de3849d
6a917f6
d5fbe40
ea9e592
71cf9af
ff68b3b
df1a988
c397a2c
aad9453
095117b
35334b9
7ef1523
2df72f3
d44d408
e5670e4
5c05a7d
3ee2b50
3bac5e1
8038d40
d1c5a11
b3b3ac6
2d6ae5a
845f43b
726f1dd
5914d31
25ee150
a0d2bcb
bcc109b
d0e3b4c
e361b8d
51f6cf2
88c2412
46be4f7
f3605be
2ff92e6
ef4d8eb
747d484
150464b
e3efab0
5c85015
d132650
23b448e
d19b8a8
f772642
45353d4
729ae9d
d6fef8b
88709a5
a1f9622
4162814
e54444a
209ef99
c3ec5f6
efb9983
43b4f53
da7acd4
7c36858
424d2e2
41d7af0
7cab803
c47714a
86db812
fa657c7
94d6a79
641496c
7fbd564
e481fca
40073f8
3609646
8dbe385
9619e74
6aaf29c
df0d205
18e4bbe
098212c
6819b1e
dcc0463
1969015
9ac3c19
e973097
474dcc5
9cb3d39
e120034
e690d77
cd674c0
8833d97
f796e1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# frozen_string_literal: true | ||
|
||
module DevValidatorSuite | ||
class ValidatorSuite < Inferno::TestSuite | ||
title 'Validator Suite' | ||
id :dev_validator | ||
description 'Inferno Core Developer Suite that makes calls to the HL7 Validator.' | ||
|
||
input :url, | ||
title: 'FHIR Server Base Url' | ||
|
||
input :access_token, | ||
title: 'Bearer/Access Token', | ||
optional: true | ||
|
||
fhir_client do | ||
url :url | ||
bearer_token :access_token | ||
end | ||
|
||
fhir_resource_validator do | ||
url 'http://localhost/hl7validatorapi' | ||
end | ||
|
||
group do | ||
title 'Patient Test Group' | ||
id :patient_group | ||
|
||
input :patient_id, | ||
title: 'Patient ID' | ||
|
||
test do | ||
title 'Patient Read Test' | ||
id :patient_read_test | ||
|
||
makes_request :patient_read | ||
|
||
run do | ||
fhir_read(:patient, patient_id, name: :patient_read) | ||
|
||
assert_response_status 200 | ||
end | ||
end | ||
|
||
test do | ||
title 'Patient Validate Test' | ||
id :patient_validate_test | ||
|
||
uses_request :patient_read | ||
|
||
run do | ||
assert_resource_type(:patient) | ||
assert_valid_resource | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
require 'thor' | ||
|
||
require_relative 'cli/main' | ||
|
||
module Inferno | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
require 'pastel' | ||
require 'active_support' | ||
require_relative '../../utils/verify_runnable' | ||
require_relative '../../utils/persist_inputs' | ||
require_relative 'execute/console_outputter' | ||
|
||
module Inferno | ||
module CLI | ||
class Execute | ||
include ::Inferno::Utils::VerifyRunnable | ||
include ::Inferno::Utils::PersistInputs | ||
|
||
attr_accessor :options | ||
|
||
def self.suppress_output | ||
begin | ||
original_stdout = $stdout.clone | ||
$stdout.reopen(File.new(File::NULL, 'w+')) | ||
retval = yield | ||
ensure | ||
$stdout.reopen(original_stdout) | ||
end | ||
retval | ||
end | ||
|
||
def self.boot_full_inferno | ||
ENV['NO_DB'] = 'false' | ||
|
||
# Inferno boot flow triggers migration and logger outputs it | ||
Inferno::CLI::Execute.suppress_output { require_relative '../../../inferno' } | ||
|
||
Inferno::Application.start(:executor) | ||
end | ||
|
||
def run(options) | ||
print_help_and_exit if options[:help] | ||
|
||
self.options = options | ||
|
||
outputter.print_start_message(options) | ||
|
||
results = [] | ||
outputter.print_around_run(options) do | ||
if selected_runnables.empty? | ||
run_one(suite) | ||
results = test_runs_repo.results_for_test_run(test_run(suite).id).reverse | ||
else | ||
selected_runnables.each do |runnable| | ||
run_one(runnable) | ||
results += test_runs_repo.results_for_test_run(test_run(runnable).id).reverse | ||
end | ||
end | ||
end | ||
|
||
outputter.print_results(options, results) | ||
|
||
outputter.print_end_message(options) | ||
|
||
exit(0) if results.all? { |result| result.result == 'pass' } | ||
|
||
# exit(1) is for Thor failures | ||
# exit(2) is for shell builtin failures | ||
exit(3) | ||
rescue Sequel::ValidationFailed => e | ||
print_error_and_exit(e, 4) | ||
rescue Sequel::ForeignKeyConstraintViolation => e | ||
print_error_and_exit(e, 5) | ||
rescue Inferno::Exceptions::RequiredInputsNotFound => e | ||
print_error_and_exit(e, 6) | ||
rescue Inferno::Exceptions::NotUserRunnableException => e | ||
print_error_and_exit(e, 7) | ||
rescue StandardError => e | ||
print_error_and_exit(e, 8) | ||
end | ||
|
||
def print_help_and_exit | ||
puts `NO_DB=true bundle exec inferno help execute` | ||
exit(0) | ||
end | ||
|
||
def outputter | ||
# TODO: swap outputter based on options | ||
@outputter ||= Inferno::CLI::Execute::ConsoleOutputter.new | ||
end | ||
|
||
def selected_runnables | ||
groups + tests | ||
end | ||
|
||
def run_one(runnable) | ||
verify_runnable( | ||
suite, | ||
thor_hash_to_inputs_array(options[:inputs]), | ||
test_session.suite_options | ||
) | ||
|
||
persist_inputs(session_data_repo, create_params(test_session, suite), test_run(runnable)) | ||
|
||
dispatch_job(test_run(runnable)) | ||
end | ||
|
||
def suite | ||
@suite ||= Inferno::Repositories::TestSuites.new.find(options[:suite]) | ||
|
||
raise StandardError, "Test suite #{options[:suite]} not found" if @suite.nil? | ||
|
||
@suite | ||
end | ||
|
||
def test_runs_repo | ||
@test_runs_repo ||= Inferno::Repositories::TestRuns.new | ||
end | ||
|
||
def test_run(runnable_param) | ||
@test_runs ||= {} | ||
|
||
@test_runs[runnable_param] ||= test_runs_repo.create( | ||
create_params(test_session, runnable_param).merge({ status: 'queued' }) | ||
) | ||
|
||
@test_runs[runnable_param] | ||
end | ||
|
||
def test_groups_repo | ||
@test_groups_repo ||= Inferno::Repositories::TestGroups.new | ||
end | ||
|
||
def tests_repo | ||
@tests_repo ||= Inferno::Repositories::Tests.new | ||
end | ||
|
||
def test_sessions_repo | ||
@test_sessions_repo ||= Inferno::Repositories::TestSessions.new | ||
end | ||
|
||
def session_data_repo | ||
@session_data_repo ||= Inferno::Repositories::SessionData.new | ||
end | ||
|
||
def test_session | ||
@test_session ||= test_sessions_repo.create({ | ||
test_suite_id: suite.id, | ||
suite_options: thor_hash_to_suite_options_array( | ||
options[:suite_options] | ||
) | ||
}) | ||
end | ||
|
||
def create_params(test_session, runnable) | ||
{ | ||
test_session_id: test_session.id, | ||
runnable_id_key(runnable) => runnable.id, | ||
inputs: thor_hash_to_inputs_array(options[:inputs]) | ||
} | ||
end | ||
|
||
def dispatch_job(test_run) | ||
# TODO: move suppression to outputter? better suppression? | ||
if options[:verbose] | ||
Jobs.perform(Jobs::ExecuteTestRun, test_run.id, force_synchronous: true) | ||
else | ||
Inferno::CLI::Execute.suppress_output do | ||
Jobs.perform(Jobs::ExecuteTestRun, test_run.id, force_synchronous: true) | ||
end | ||
end | ||
end | ||
|
||
def groups | ||
return [] if options[:groups].blank? | ||
|
||
@groups ||= options[:groups]&.map { |short_id| find_by_short_id!(test_groups_repo, short_id) } | ||
end | ||
|
||
def tests | ||
return [] if options[:tests].blank? | ||
|
||
@tests ||= options[:tests]&.map { |short_id| find_by_short_id!(tests_repo, short_id) } | ||
end | ||
|
||
def find_by_short_id!(repo, short_id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't make a bang method if there isn't a version without a bang. |
||
repo.all.each do |entity| | ||
return entity if short_id == entity.short_id && suite.id == entity.suite.id | ||
end | ||
raise StandardError, "Group or test #{short_id} not found" | ||
end | ||
|
||
def thor_hash_to_suite_options_array(hash = {}) | ||
hash.to_a.map { |pair| Inferno::DSL::SuiteOption.new({ id: pair[0], value: pair[1] }) } | ||
end | ||
|
||
def thor_hash_to_inputs_array(hash = {}) | ||
hash.to_a.map { |pair| { name: pair[0], value: pair[1] } } | ||
end | ||
|
||
def print_error_and_exit(err, code) | ||
outputter.print_error(options || {}, err) | ||
rescue StandardError => e | ||
puts "Caught exception #{e} while printing exception #{err}. Exiting." | ||
ensure | ||
exit(code) | ||
end | ||
|
||
def runnable_type(runnable) | ||
if Inferno::TestSuite.subclasses.include? runnable | ||
:suite | ||
elsif Inferno::TestGroup.subclasses.include? runnable | ||
:group | ||
elsif Inferno::Test.subclasses.include? runnable | ||
:test | ||
else | ||
raise StandardError, "Unidentified runnable #{runnable}" | ||
end | ||
end | ||
|
||
def runnable_id_key(runnable) | ||
case runnable_type(runnable) | ||
when :suite | ||
:test_suite_id | ||
when :group | ||
:test_group_id | ||
else | ||
:test_id | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
module Inferno | ||
module CLI | ||
class Execute | ||
# Subclass AbstractOutputter to implement your own outputter | ||
# | ||
# @abstract | ||
# rubocop:disable Lint/UnusedMethodArgument | ||
class AbstractOutputter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should add abstract classes. If you think we need some sort of really basic output class, make this some sort of null output class that works but doesn't print anything. |
||
# @see Inferno::CLI::Main#execute for options | ||
def print_start_message(options) | ||
raise StandardError, 'not implemented' | ||
end | ||
|
||
# Implementation MUST call `yield` | ||
# @see Inferno::CLI::Main#execute for options | ||
def print_around_run(options, &) | ||
raise StandardError, 'not implemented' | ||
end | ||
|
||
# @see Inferno::CLI::Main#execute for options | ||
# @param results [Array<Inferno::Entity::Result>] | ||
def print_results(options, results) | ||
raise StandardError, 'not implemented' | ||
end | ||
|
||
# @see Inferno::CLI::Main#execute for options | ||
def print_end_message(options) | ||
raise StandardError, 'not implemented' | ||
end | ||
|
||
# Implementation MUST NOT re-raise exception or exit | ||
# @see Inferno::CLI::Main#execute for options | ||
# @param exception [Exception] | ||
def print_error(options, exception) | ||
raise StandardError, 'not implemented' | ||
end | ||
end | ||
# rubocop:enable Lint/UnusedMethodArgument | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is doing too much. Display and execution concerns should be separated. This would get even worse once we have multiple output formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to refactor it in this PR? I could also do it in a JSON-only-output PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored!