Skip to content
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 2962 execute outputters #541

Merged
merged 32 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
288794a
add quiet outputter
Shaumik-Ashraf Oct 7, 2024
09916bf
add plain outputter
Shaumik-Ashraf Oct 7, 2024
c6259e1
impl json outputters and outputter option
Shaumik-Ashraf Oct 7, 2024
6e91e59
debug
Shaumik-Ashraf Oct 8, 2024
dc863a5
spec and cop
Shaumik-Ashraf Oct 8, 2024
92c931a
keep job silencer in execute; add spinner
Shaumik-Ashraf Oct 8, 2024
47fed6d
write outputter_spec and json_outputter_spec
Shaumik-Ashraf Oct 8, 2024
49f9587
spec plain and quiet outputters
Shaumik-Ashraf Oct 8, 2024
7576474
cop
Shaumik-Ashraf Oct 8, 2024
c3d682f
add in-code documentation
Shaumik-Ashraf Oct 8, 2024
ab6723a
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Oct 10, 2024
e07fdd7
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Oct 11, 2024
590be67
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Oct 16, 2024
eef78ab
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Oct 18, 2024
1fb7e4a
move tty-spinner gem to gemspec
Shaumik-Ashraf Oct 30, 2024
b7aadbd
replace OUTPUTTERS_WHITELIST with OUTPUTTERS hash
Shaumik-Ashraf Oct 30, 2024
86b000b
remove redundant backtrace print
Shaumik-Ashraf Oct 30, 2024
65a177c
fix quiet outputter unused variables
Shaumik-Ashraf Oct 30, 2024
fcd2a1c
cop
Shaumik-Ashraf Oct 30, 2024
7ae50ec
resolve merge conflict with main
Shaumik-Ashraf Oct 30, 2024
f7f0d74
debug
Shaumik-Ashraf Oct 30, 2024
334867f
fix docs
Shaumik-Ashraf Oct 30, 2024
4e1d4ce
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Nov 1, 2024
5770bb8
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Nov 4, 2024
d8b1052
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Nov 5, 2024
5618f4e
remove serialize standard error; use demodulize
Shaumik-Ashraf Nov 13, 2024
df5f0b2
factorize serializer and remove JSONOutputter from ConsoleOutputter a…
Shaumik-Ashraf Nov 13, 2024
aedfe06
create serialize_spec, rspec, and cop
Shaumik-Ashraf Nov 14, 2024
3d8ff0f
Merge branch 'main' into fi-2962-execute-outputters
Shaumik-Ashraf Nov 14, 2024
96fa788
yank spinner
Shaumik-Ashraf Nov 15, 2024
de121a3
rm tty-spinner gem
Shaumik-Ashraf Nov 15, 2024
157b3c3
cop
Shaumik-Ashraf Nov 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ group :test do
gem 'simplecov-cobertura'
gem 'webmock'
gem 'factory_bot'
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would have to go in the gemspec.

gem "tty-spinner", "~> 0.9.3"
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ GEM
thor (1.2.2)
tilt (2.4.0)
tty-color (0.6.0)
tty-cursor (0.7.1)
tty-markdown (0.7.2)
kramdown (>= 1.16.2, < 3.0)
pastel (~> 0.8)
Expand All @@ -297,6 +298,8 @@ GEM
tty-color (~> 0.5)
tty-screen (~> 0.8)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.1.0)
Expand Down Expand Up @@ -335,6 +338,7 @@ DEPENDENCIES
rubocop-sequel
simplecov
simplecov-cobertura
tty-spinner (~> 0.9.3)
webmock
yard
yard-junk
Expand Down
22 changes: 17 additions & 5 deletions lib/inferno/apps/cli/execute.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
require 'pastel'
require 'active_support'
require 'dry/inflector'
require_relative '../../utils/verify_runnable'
require_relative '../../utils/persist_inputs'
require_relative 'execute/console_outputter'

Dir[File.join(__dir__, 'execute', '*_outputter.rb')].each { |outputter| require outputter }

module Inferno
module CLI
class Execute
include ::Inferno::Utils::VerifyRunnable
include ::Inferno::Utils::PersistInputs

INFLECTOR = Dry::Inflector.new do |inflections|
inflections.acronym 'JSON'
end

OUTPUTTER_WHITELIST = %w[console plain json quiet].freeze

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all fine, but a Hash mapping the option to the outputter class would be a lot simpler. I don't think being dynamic buys us much here, since we would need to alter code here, as well as documentation when adding new outputters anyway.

attr_accessor :options

def self.suppress_output
Expand Down Expand Up @@ -79,8 +86,14 @@ def print_help_and_exit
end

def outputter
# TODO: swap outputter based on options
@outputter ||= Inferno::CLI::Execute::ConsoleOutputter.new
unless OUTPUTTER_WHITELIST.include? options[:outputter]
raise StandardError,
"Unrecognized outputter #{options[:outputter]}"
end

@outputter ||= INFLECTOR.constantize(
"Inferno::CLI::Execute::#{INFLECTOR.camelize(options[:outputter])}Outputter"
).new
end

def selected_runnables
Expand Down Expand Up @@ -155,7 +168,6 @@ def create_params(test_session, runnable)
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
Expand Down
49 changes: 20 additions & 29 deletions lib/inferno/apps/cli/execute/console_outputter.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
require 'pastel'
require_relative '../../web/serializers/test_run'
require_relative '../../web/serializers/result'
require 'tty-spinner'
require_relative 'json_outputter'

module Inferno
module CLI
class Execute
# @private
class ConsoleOutputter
COLOR = Pastel.new
class ConsoleOutputter < JSONOutputter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, I don't think it makes sense for this to inherit from JSONOutputter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It inherits serialize

CHECKMARK = "\u2713".freeze
BAR = ('=' * 80).freeze
SPINNER = TTY::Spinner.new('Running tests [:spinner]', format: :bouncing_ball, clear: true, output: $stdout)

def print_start_message(options)
puts ''
Expand All @@ -19,9 +19,9 @@ def print_start_message(options)
end

def print_around_run(_options)
puts 'Running tests. This may take a while...'
# TODO: spinner/progress bar
SPINNER.auto_spin
yield
SPINNER.stop('done!')
end

def print_results(options, results)
Expand All @@ -44,14 +44,18 @@ def print_results(options, results)
def print_end_message(options); end

def print_error(options, exception)
puts COLOR.red "Error: #{exception.full_message}"
puts color.red "Error: #{exception.full_message}"
verbose_print(options, exception.backtrace&.join('\n'))
end

# private

def verbose_print(options, *args)
print(COLOR.dim(*args)) if options[:verbose]
print(color.dim(*args)) if options[:verbose]
end

def color
@color ||= Pastel.new(enabled: $stdout.tty?)
end

def verbose_puts(options, *args)
Expand Down Expand Up @@ -103,21 +107,21 @@ def format_outputs(result)
def format_result(result) # rubocop:disable Metrics/CyclomaticComplexity
case result.result
when 'pass'
COLOR.bold.green(CHECKMARK, ' pass')
color.bold.green(CHECKMARK, ' pass')
when 'fail'
COLOR.bold.red 'X fail'
color.bold.red 'X fail'
when 'skip'
COLOR.yellow '* skip'
color.yellow '* skip'
when 'omit'
COLOR.blue '* omit'
color.blue '* omit'
when 'error'
COLOR.magenta 'X error'
color.magenta 'X error'
when 'wait'
COLOR.bold '. wait'
color.bold '. wait'
when 'cancel'
COLOR.red 'X cancel'
color.red 'X cancel'
when 'running'
COLOR.bold '- running'
color.bold '- running'
else
raise StandardError.new, "Unrecognized result #{result.result}"
end
Expand All @@ -130,19 +134,6 @@ def verbose_print_json_results(options, results)
verbose_puts(options, serialize(results))
verbose_puts(options, BAR)
end

def serialize(entity)
case entity.class.to_s
when 'Array'
JSON.pretty_generate(entity.map { |item| JSON.parse serialize(item) })
when lambda { |x|
defined?(x.constantize) && defined?("Inferno::Web::Serializers::#{x.split('::').last}".constantize)
}
"Inferno::Web::Serializers::#{entity.class.to_s.split('::').last}".constantize.render(entity)
else
raise StandardError, "CLI does not know how to serialize #{entity.class}"
end
end
end
end
end
Expand Down
40 changes: 40 additions & 0 deletions lib/inferno/apps/cli/execute/json_outputter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require_relative '../../web/serializers/test_run'
require_relative '../../web/serializers/result'

module Inferno
module CLI
class Execute
# @private
class JSONOutputter
def print_start_message(_options); end

def print_around_run(_options, &)
yield
end

def print_results(_options, results)
puts serialize(results)
end

def print_end_message(_options); end

def print_error(_options, exception)
puts exception.to_json
end

def serialize(entity)
case entity.class.to_s
when 'Array'
JSON.pretty_generate(entity.map { |item| JSON.parse serialize(item) })
when lambda { |x|
defined?(x.constantize) && defined?("Inferno::Web::Serializers::#{x.split('::').last}".constantize)
}
"Inferno::Web::Serializers::#{entity.class.to_s.split('::').last}".constantize.render(entity)
else
raise StandardError, "CLI does not know how to serialize #{entity.class}"
end
end
end
end
end
end
25 changes: 25 additions & 0 deletions lib/inferno/apps/cli/execute/plain_outputter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require_relative 'console_outputter'

module Inferno
module CLI
class Execute
# @private
class PlainOutputter < ConsoleOutputter
# override to disable spinner
def print_around_run(_options)
puts 'Running tests. This may take a while...'
yield
end

def print_error(_options, exception)
puts "Error: #{exception.full_message(highlight: false)}"
puts exception.backtrace&.join('\n')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this different from the console outputter version? Also, doesn't full_message already include the stack trace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plain outputter needs highlight: false or it outputs ANSI code \e[1 (to make it bold).

But the backtrace part was redundant so I'm removing that.

end

def color
@color ||= Pastel.new(enabled: false)
end
end
end
end
end
24 changes: 24 additions & 0 deletions lib/inferno/apps/cli/execute/quiet_outputter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Inferno
module CLI
class Execute
# @private
class QuietOutputter
# rubocop:disable Lint/UnusedMethodArgument
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't disable this rule. Prefix the arguments with underscore if they're not used.

def print_start_message(options); end

def print_around_run(options, &)
yield
end

def print_results(options, results); end

def print_end_message(options); end

def print_error(options, exception)
puts "Error: #{exception.full_message}" if options[:verbose]
end
# rubocop:enable Lint/UnusedMethodArgument
end
end
end
end
16 changes: 15 additions & 1 deletion lib/inferno/apps/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ def version

You can view suite ids with: `bundle exec inferno suites`

You can select an output format with the `--outputter` option. Current outputters
are console (default), plain, quiet, and json. JSON-formatted output will copy
Inferno's REST API: https://inferno-framework.github.io/inferno-core/api-docs/#/Result.

Examples:

`bundle exec inferno execute --suite dev_validator \
Expand All @@ -86,6 +90,12 @@ def version
patient_id:1234321 \
--tests 1.01 1.02`
=> Run specific tests from suite

`bundle exec inferno execute --suite dev_validator \
--inputs "url:https://hapi.fhir.org/baseR4" \
patient_id:1234321` \
--outputter json
=> Outputs test results in JSON
END_OF_HELP
desc 'execute', 'Run Inferno tests in command line'
long_desc EXECUTE_HELP, wrap: false
Expand All @@ -95,7 +105,7 @@ def version
desc: 'Test suite id to run or to select groups and tests from',
banner: 'id'
option :suite_options,
aliases: ['-u'], # NOTE: -o will be for outputter
aliases: ['-u'],
type: :hash,
desc: 'Suite options'
option :groups,
Expand All @@ -110,6 +120,10 @@ def version
aliases: ['-i'],
type: :hash,
desc: 'Inputs (i.e: --inputs=foo:bar goo:baz)'
option :outputter,
aliases: ['-o'],
default: 'console',
desc: 'Select an outputter format: console | plain | json | quiet'
option :verbose,
aliases: ['-v'],
type: :boolean,
Expand Down
13 changes: 2 additions & 11 deletions spec/inferno/apps/cli/execute/console_outputter_spec.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
require_relative '../../../../../lib/inferno/apps/cli/execute/console_outputter'
require_relative 'outputter_spec'

RSpec.describe Inferno::CLI::Execute::ConsoleOutputter do # rubocop:disable RSpec/FilePath
let(:instance) { described_class.new }
let(:options) { { verbose: true } }

describe '#serialize' do
let(:test_results) { create_list(:result, 2) }

it 'handles an array of test results without raising exception' do
expect { instance.serialize(test_results) }.to_not raise_error(StandardError)
end

it 'returns valid JSON' do
expect { JSON.parse(instance.serialize(test_results)) }.to_not raise_error(StandardError)
end
end
include_examples 'outputter_spec', described_class

describe '#verbose_print' do
it 'outputs when verbose is true' do
Expand Down
21 changes: 21 additions & 0 deletions spec/inferno/apps/cli/execute/json_outputter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require_relative '../../../../../lib/inferno/apps/cli/execute/json_outputter'
require_relative 'outputter_spec'

RSpec.describe Inferno::CLI::Execute::JSONOutputter do # rubocop:disable RSpec/FilePath
let(:instance) { described_class.new }
let(:options) { { outputter: 'json', verbose: false } }

include_examples 'outputter_spec', described_class

describe '#serialize' do
let(:test_results) { create_list(:result, 2) }

it 'handles an array of test results without raising exception' do
expect { instance.serialize(test_results) }.to_not raise_error(StandardError)
end

it 'returns JSON' do
expect { JSON.parse(instance.serialize(test_results)) }.to_not raise_error(StandardError)
end
end
end
Loading
Loading