Module: Ibex::CLICoverage
- Defined in:
- lib/ibex/cli/coverage.rb,
sig/ibex/cli/coverage.rbs
Overview
CLI collection, merge, and threshold checks for runtime coverage.
Instance Method Summary collapse
- #atomic_write_coverage(path, source) ⇒ void
- #coverage_check_options(arguments) ⇒ coverage_check_options
- #coverage_file_mode(path) ⇒ Integer
- #coverage_output_options(arguments, command, operands) ⇒ coverage_output_options
- #coverage_percentage(covered, total) ⇒ Float
- #coverage_threshold(value, name) ⇒ Float
- #reject_coverage_output_collision(inputs, output) ⇒ void
- #run_coverage_check(arguments) ⇒ Integer
- #run_coverage_collect(arguments) ⇒ Integer
- #run_coverage_command(arguments) ⇒ Integer
- #run_coverage_merge(arguments) ⇒ Integer
- #single_coverage_path(arguments, command) ⇒ String
- #write_coverage_report(report, output) ⇒ void
Instance Method Details
#atomic_write_coverage(path, source) ⇒ void
This method returns an undefined value.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/ibex/cli/coverage.rb', line 151 def atomic_write_coverage(path, source) target_path = File.symlink?(path) ? File.realpath(path) : path directory = File.dirname(File.(target_path)) basename = File.basename(target_path) Tempfile.create([".#{basename}.", ".tmp"], directory) do |temporary| temporary.binmode temporary.write(source) temporary.flush temporary.fsync temporary.chmod(coverage_file_mode(target_path)) temporary.close File.rename(temporary.path, target_path) end end |
#coverage_check_options(arguments) ⇒ coverage_check_options
98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/ibex/cli/coverage.rb', line 98 def (arguments) settings = { paths: [], min_states: 0.0, min_productions: 0.0 } #: coverage_check_options parser = OptionParser.new do || . = "Usage: ibex coverage check REPORT [thresholds]" .on("--min-states=PERCENT", Float, "minimum visited states") do |value| settings[:min_states] = coverage_threshold(value, "min-states") end .on("--min-productions=PERCENT", Float, "minimum reduced productions") do |value| settings[:min_productions] = coverage_threshold(value, "min-productions") end end settings[:paths] = parser.parse(arguments) settings end |
#coverage_file_mode(path) ⇒ Integer
167 168 169 170 171 |
# File 'lib/ibex/cli/coverage.rb', line 167 def coverage_file_mode(path) return File.stat(path).mode & 0o777 if File.exist?(path) 0o666 & ~File.umask end |
#coverage_output_options(arguments, command, operands) ⇒ coverage_output_options
87 88 89 90 91 92 93 94 95 |
# File 'lib/ibex/cli/coverage.rb', line 87 def (arguments, command, operands) settings = { paths: [] } #: coverage_output_options parser = OptionParser.new do || . = "Usage: ibex coverage #{command} #{operands} [-o FILE]" .on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value } end settings[:paths] = parser.parse(arguments) settings end |
#coverage_percentage(covered, total) ⇒ Float
121 122 123 124 125 |
# File 'lib/ibex/cli/coverage.rb', line 121 def coverage_percentage(covered, total) return 100.0 if total.zero? covered.fdiv(total) * 100.0 end |
#coverage_threshold(value, name) ⇒ Float
114 115 116 117 118 |
# File 'lib/ibex/cli/coverage.rb', line 114 def coverage_threshold(value, name) return value if value.finite? && value >= 0.0 && value <= 100.0 raise OptionParser::InvalidArgument, "--#{name} must be between 0 and 100" end |
#reject_coverage_output_collision(inputs, output) ⇒ void
This method returns an undefined value.
135 136 137 138 139 140 |
# File 'lib/ibex/cli/coverage.rb', line 135 def reject_coverage_output_collision(inputs, output) return unless output return unless inputs.any? { |input| same_file_target?(input, output) } raise Ibex::Error, "(cli):1:1: coverage input and output paths must be distinct" end |
#run_coverage_check(arguments) ⇒ Integer
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ibex/cli/coverage.rb', line 63 def run_coverage_check(arguments) settings = (arguments) path = single_coverage_path(settings.fetch(:paths), "coverage check") report = Coverage::Report.load_file(path) state_percentage = coverage_percentage(report.state_hits.length, report.state_count) production_percentage = coverage_percentage(report.production_hits.length, report.production_count) passed = state_percentage >= settings.fetch(:min_states) && production_percentage >= settings.fetch(:min_productions) summary = format( "coverage %<status>s: states %<states>.2f%% (%<state_hits>d/%<state_total>d), " \ "productions %<productions>.2f%% (%<production_hits>d/%<production_total>d)", status: passed ? "ok" : "failed", states: state_percentage, state_hits: report.state_hits.length, state_total: report.state_count, productions: production_percentage, production_hits: report.production_hits.length, production_total: report.production_count ) (passed ? @stdout : @stderr).puts(summary) passed ? 0 : 1 end |
#run_coverage_collect(arguments) ⇒ Integer
38 39 40 41 42 43 44 45 |
# File 'lib/ibex/cli/coverage.rb', line 38 def run_coverage_collect(arguments) settings = (arguments, "collect", "INPUT") input = single_coverage_path(settings.fetch(:paths), "coverage collect") reject_coverage_output_collision([input], settings[:output]) report = Coverage::Collector.collect_file(input) write_coverage_report(report, settings[:output]) 0 end |
#run_coverage_command(arguments) ⇒ Integer
26 27 28 29 30 31 32 33 34 35 |
# File 'lib/ibex/cli/coverage.rb', line 26 def run_coverage_command(arguments) operation = arguments.shift case operation when "collect" then run_coverage_collect(arguments) when "merge" then run_coverage_merge(arguments) when "check" then run_coverage_check(arguments) else raise Ibex::Error, "(cli):1:1: coverage requires collect, merge, or check" end end |
#run_coverage_merge(arguments) ⇒ Integer
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/ibex/cli/coverage.rb', line 48 def run_coverage_merge(arguments) settings = (arguments, "merge", "INPUT...") inputs = settings.fetch(:paths) raise Ibex::Error, "(cli):1:1: coverage merge requires at least one input" if inputs.empty? reject_coverage_output_collision(inputs, settings[:output]) reports = inputs.map { |path| Coverage::Report.load_file(path) } report = Coverage::Report.merge(reports) write_coverage_report(report, settings[:output]) 0 rescue ArgumentError => e raise Ibex::Error, "(coverage):1:1: #{e.}" end |
#single_coverage_path(arguments, command) ⇒ String
128 129 130 131 132 |
# File 'lib/ibex/cli/coverage.rb', line 128 def single_coverage_path(arguments, command) return arguments.first if arguments.length == 1 raise Ibex::Error, "(cli):1:1: #{command} requires exactly one input" end |
#write_coverage_report(report, output) ⇒ void
This method returns an undefined value.
143 144 145 146 147 148 |
# File 'lib/ibex/cli/coverage.rb', line 143 def write_coverage_report(report, output) source = report.to_json return @stdout.write(source) unless output atomic_write_coverage(output, source) end |