Class: Necropsy::RuntimeFeedback
- Inherits:
-
Object
- Object
- Necropsy::RuntimeFeedback
- Defined in:
- lib/necropsy/runtime_feedback.rb
Overview
Compares positive runtime target observations with the static target set.
Runtime observations may discover a target that the static resolver did not enumerate. That is a safety signal and a useful regression fixture, but an unobserved static target is never removed from the model.
Constant Summary collapse
- SCHEMA_VERSION =
1- DEFAULT_FIXTURE_LIMIT =
32
Class Method Summary collapse
Instance Method Summary collapse
- #call ⇒ Object
-
#initialize(static_targets:, observed_targets:, max_fixtures: DEFAULT_FIXTURE_LIMIT) ⇒ RuntimeFeedback
constructor
A new instance of RuntimeFeedback.
- #write_fixture_candidates(path) ⇒ Object
Constructor Details
#initialize(static_targets:, observed_targets:, max_fixtures: DEFAULT_FIXTURE_LIMIT) ⇒ RuntimeFeedback
Returns a new instance of RuntimeFeedback.
16 17 18 19 20 21 22 23 24 25 |
# File 'lib/necropsy/runtime_feedback.rb', line 16 def initialize(static_targets:, observed_targets:, max_fixtures: DEFAULT_FIXTURE_LIMIT) @static_targets = normalize_static_targets(static_targets) @observed_targets = normalize_observed_targets(observed_targets) @max_fixtures = Integer(max_fixtures) raise ArgumentError, 'max_fixtures must be non-negative' if @max_fixtures.negative? rescue ArgumentError, TypeError => e raise e unless e..start_with?('invalid value for Integer()', 'no implicit conversion of') raise ArgumentError, 'max_fixtures must be a non-negative integer' end |
Class Method Details
.from_graph(graph, observed_targets:, max_fixtures: DEFAULT_FIXTURE_LIMIT) ⇒ Object
78 79 80 81 82 83 |
# File 'lib/necropsy/runtime_feedback.rb', line 78 def self.from_graph(graph, observed_targets:, max_fixtures: DEFAULT_FIXTURE_LIMIT) static_targets = graph.resolution_records.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |record, result| result[record.resolution.call_site_id].concat(record.resolution.target_definition_ids) end new(static_targets: static_targets, observed_targets: observed_targets, max_fixtures: max_fixtures) end |
Instance Method Details
#call ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/necropsy/runtime_feedback.rb', line 27 def call observed_by_site = @observed_targets.group_by { |target| target.fetch('call_site_id') } static_by_site = @static_targets missing_static = @observed_targets.filter_map do |target| static = static_by_site.fetch(target.fetch('call_site_id'), []) next if static.include?(target.fetch('target_definition_id')) target.merge( 'classification' => 'missing_static_target', 'safety' => 'safety_bug' ) end fixtures = missing_static.first(@max_fixtures).map { |target| fixture_candidate(target) } unobserved_static = static_by_site.flat_map do |call_site_id, targets| observed = observed_by_site.fetch(call_site_id, []).map { |target| target.fetch('target_definition_id') } targets.reject { |target| observed.include?(target) }.map do |target| { 'call_site_id' => call_site_id, 'target_definition_id' => target, 'informational' => true } end end { 'schema_version' => SCHEMA_VERSION, 'policy' => 'positive_only', 'comparison' => { 'call_sites' => (static_by_site.keys | observed_by_site.keys).length, 'static_target_count' => static_by_site.values.sum(&:length), 'observed_target_count' => @observed_targets.length }, 'missing_static_targets' => missing_static, 'unexpected_targets' => missing_static, 'unobserved_static_targets' => unobserved_static, 'fixture_candidates' => fixtures, 'fixture_exported' => false } end |
#write_fixture_candidates(path) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/necropsy/runtime_feedback.rb', line 63 def write_fixture_candidates(path) payload = call candidates = payload.fetch('fixture_candidates') destination = File.(path) FileUtils.mkdir_p(File.dirname(destination)) temporary = "#{destination}.tmp-#{Process.pid}-#{Thread.current.object_id}" File.write(temporary, "#{JSON.pretty_generate(candidates)}\n") File.rename(temporary, destination) payload.merge('fixture_exported' => true, 'fixture_path' => destination) rescue SystemCallError => e raise Error, "Could not export runtime feedback fixtures: #{e.}" ensure FileUtils.rm_f(temporary) if temporary && File.exist?(temporary) end |