Class: Ast::Merge::Git::LocalBenchmark

Inherits:
Object
  • Object
show all
Defined in:
lib/ast/merge/git/local_benchmark.rb,
sig/ast/merge/git.rbs

Overview

Offline Slice 1022-compatible corpus validation and deterministic selection. rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- benchmark evidence is intentionally explicit

Defined Under Namespace

Classes: Error

Constant Summary collapse

SCHEMA =

Returns:

  • (String)
'structuredmerge.benchmark.corpus/v1'
CASE_SCHEMA =

Returns:

  • (String)
'structuredmerge.benchmark/v1'
OPERATIONS =

Returns:

  • (Array[String])
%w[merge2 merge3 metamorphic diff].freeze
EXECUTABLE_OPERATIONS =
%w[merge2 merge3 metamorphic].freeze
PARTITIONS =

Returns:

  • (Array[String])
%w[sentinel gold metamorphic].freeze
EXPECTATIONS =

Returns:

  • (Array[String])
%w[clean conflict error excluded_ambiguous].freeze
SEVERITIES =

Returns:

  • (Array[String])
%w[none low high critical].freeze
PRESERVATION =

Returns:

  • (Array[String])
%w[required allowed_to_change not_applicable].freeze
TRANSFORMATIONS =

Returns:

  • (Array[String])
%w[
  rename move reorder formatting comment independent_edit delete_modify duplicate_key_identity
  schema_aware_mutation
].freeze
METAMORPHIC_INVARIANTS =
%w[
  comment-retained no-semantic-edit same-json-value same-jsonc-value
].freeze
ID_PATTERN =

Returns:

  • (Regexp)
/\A[a-z0-9]+(?:[.-][a-z0-9]+)*\z/
PROVENANCE_FIELDS =

Returns:

  • (Array[String])
%w[
  origin_uri revision spdx_license license_evidence_uri authorship author_review reviewer derivation
].freeze
SELECTOR_FIELDS =

Returns:

  • (Array[String])
%w[provider_id family dialect backend profile require].freeze
INPUT_ROLES =

Returns:

  • (Hash[String, Array[String]])
{
  'merge2' => %w[incoming current],
  'merge3' => %w[base ours theirs],
  'metamorphic' => %w[source transformed],
  'diff' => %w[before after]
}.freeze
PROFILE_POLICIES =

Returns:

  • (Hash[String, Array[String]])
{
  'micro' => %w[sentinels none],
  'dev' => %w[affected none],
  'nightly' => %w[all none],
  'competitive' => %w[all configured]
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document, path: nil, corpus_digest: nil) ⇒ LocalBenchmark

Returns a new instance of LocalBenchmark.

Parameters:

  • document (Hash[String, untyped])
  • path: (String, Pathname, nil) (defaults to: nil)
  • corpus_digest: (String, nil) (defaults to: nil)


61
62
63
64
65
# File 'lib/ast/merge/git/local_benchmark.rb', line 61

def initialize(document, path: nil, corpus_digest: nil)
  @document = document
  @path = path && Pathname(path).expand_path
  @corpus_digest = corpus_digest || digest(canonical_json(document))
end

Instance Attribute Details

#corpus_digest ⇒ String (readonly)

Returns the value of attribute corpus_digest.

Returns:

  • (String)


50
51
52
# File 'lib/ast/merge/git/local_benchmark.rb', line 50

def corpus_digest
  @corpus_digest
end

#document ⇒ Hash[String, untyped] (readonly)

Returns the value of attribute document.

Returns:

  • (Hash[String, untyped])


50
51
52
# File 'lib/ast/merge/git/local_benchmark.rb', line 50

def document
  @document
end

#path ⇒ Pathname? (readonly)

Returns the value of attribute path.

Returns:

  • (Pathname, nil)


50
51
52
# File 'lib/ast/merge/git/local_benchmark.rb', line 50

def path
  @path
end

Class Method Details

.load(path) ⇒ LocalBenchmark

Parameters:

  • path (String, Pathname)

Returns:



52
53
54
55
56
57
58
59
# File 'lib/ast/merge/git/local_benchmark.rb', line 52

def self.load(path)
  source = File.binread(path)
  new(JSON.parse(source), path: path, corpus_digest: Digest::SHA256.hexdigest(source)).tap(&:validate!)
rescue JSON::ParserError => e
  raise Error, "invalid corpus JSON: #{e.message}"
rescue SystemCallError => e
  raise Error, "cannot read corpus: #{e.message}"
end

Instance Method Details

#case_by_id(id) ⇒ Hash[String, untyped]

Parameters:

  • id (String)

Returns:

  • (Hash[String, untyped])


148
149
150
# File 'lib/ast/merge/git/local_benchmark.rb', line 148

def case_by_id(id)
  document.fetch('cases').find { |item| item['id'] == id } || error!("unknown case: #{id}")
end

#cases ⇒ Array[Hash[String, untyped]]

Returns:

  • (Array[Hash[String, untyped]])


83
84
85
86
# File 'lib/ast/merge/git/local_benchmark.rb', line 83

def cases
  validate!
  document.fetch('cases')
end

#select(profile:, changed_paths: []) ⇒ Hash[String, untyped]

Parameters:

  • profile: (String, Symbol)
  • changed_paths: (Array[String]) (defaults to: [])

Returns:

  • (Hash[String, untyped])


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/ast/merge/git/local_benchmark.rb', line 88

def select(profile:, changed_paths: [])
  validate!
  profile = profile.to_s
  definition = document.fetch('profiles')[profile]
  error!("unknown profile: #{profile}") unless definition
  paths = changed_paths.map(&:to_s).uniq.sort
  inferred = paths.to_h { |changed| [changed, capabilities_for(changed)] }
  capabilities = inferred.values.flatten.uniq.sort
  direct = cases.select { |item| direct_case?(item, capabilities) }.map { |item| item.fetch('id') }
  sentinels = definition.fetch('mandatory_sentinels')
  base = ordered(sentinels + direct)
  population = cases.map { |item| item.fetch('id') } - base
  neighbors = if definition.fetch('selection_mode') == 'affected'
                neighbor_order(population).first(definition.fetch('neighbor_count'))
              else
                []
              end
  selected = selected_ids_for(definition.fetch('selection_mode'), sentinels, base, neighbors)
  if selected.length > definition.dig('budgets', 'case_count')
    error!("profile #{profile} case budget does not fit selection")
  end

  {
    'profile' => profile,
    'selection_mode' => definition.fetch('selection_mode'),
    'competitor_policy' => definition.fetch('competitor_policy'),
    'seed' => document.dig('selection', 'seed'),
    'selected_case_ids' => selected,
    'excluded_case_ids' => cases.map { |item| item.fetch('id') } - selected,
    'changed_paths' => inferred.map { |changed, caps| { 'path' => changed, 'capabilities' => caps } },
    'inferred_capabilities' => capabilities,
    'direct_cases' => direct,
    'direct_case_reasons' => capabilities.to_h do |capability|
      matching = cases.filter_map do |item|
        item['id'] if direct_case?(item, [capability])
      end
      [capability, matching]
    end,
    'sentinels' => sentinels,
    'neighbor_sample' => {
      'population' => population,
      'ordering_algorithm' => document.dig('selection', 'neighbor_order'),
      'seed' => document.dig('selection', 'seed'),
      'selected_case_ids' => neighbors
    },
    'unsupported_selected_cases' => selected.reject do |id|
      EXECUTABLE_OPERATIONS.include?(case_by_id(id)['operation'])
    end,
    'budgets' => definition.fetch('budgets'),
    'explanation' => explanation(
      definition.fetch('selection_mode'),
      inferred: inferred,
      direct: direct,
      sentinels: sentinels,
      population: population,
      neighbors: neighbors
    )
  }
end

#validate! ⇒ true

Returns:

  • (true)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ast/merge/git/local_benchmark.rb', line 67

def validate!
  require_keys(document, %w[schema_version kind id version extends provenance profiles capability_map
                            selection competitors cases expected_summary], 'corpus')
  error!('unsupported corpus schema') unless document['schema_version'] == SCHEMA
  error!('corpus must extend Slice 1022 v1') unless document['extends'] == CASE_SCHEMA
  error!('network must be denied') unless document['network_policy'] == 'denied'
  error!('services must be empty') unless document['services'] == []
  validate_provenance!(document['provenance'], 'corpus')
  validate_profiles!
  validate_capability_map!
  validate_competitors!
  validate_cases!
  validate_summary!
  true
end