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[merge3 metamorphic diff].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
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]])
{
  'merge3' => %w[base ours theirs],
  'metamorphic' => %w[source transformed],
  'diff' => %w[before after]
}.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)


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

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_digestString (readonly)

Returns the value of attribute corpus_digest.

Returns:

  • (String)


38
39
40
# File 'lib/ast/merge/git/local_benchmark.rb', line 38

def corpus_digest
  @corpus_digest
end

#documentHash[String, untyped] (readonly)

Returns the value of attribute document.

Returns:

  • (Hash[String, untyped])


38
39
40
# File 'lib/ast/merge/git/local_benchmark.rb', line 38

def document
  @document
end

#pathPathname? (readonly)

Returns the value of attribute path.

Returns:

  • (Pathname, nil)


38
39
40
# File 'lib/ast/merge/git/local_benchmark.rb', line 38

def path
  @path
end

Class Method Details

.load(path) ⇒ LocalBenchmark

Parameters:

  • path (String, Pathname)

Returns:



40
41
42
43
44
45
46
47
# File 'lib/ast/merge/git/local_benchmark.rb', line 40

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])


125
126
127
# File 'lib/ast/merge/git/local_benchmark.rb', line 125

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

#casesArray[Hash[String, untyped]]

Returns:

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


70
71
72
73
# File 'lib/ast/merge/git/local_benchmark.rb', line 70

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])


75
76
77
78
79
80
81
82
83
84
85
86
87
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
# File 'lib/ast/merge/git/local_benchmark.rb', line 75

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')
  selected = ordered(sentinels + direct)
  population = cases.map { |item| item.fetch('id') } - selected
  neighbors = neighbor_order(population).first(definition.fetch('neighbor_count'))
  selected = ordered(selected + neighbors)
  selected = sentinels if profile == 'micro'

  {
    'profile' => profile,
    '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 { |id| case_by_id(id)['operation'] == 'merge3' },
    'budgets' => definition.fetch('budgets'),
    'explanation' => explanation(
      profile,
      inferred: inferred,
      direct: direct,
      sentinels: sentinels,
      population: population,
      neighbors: neighbors
    )
  }
end

#validate!true

Returns:

  • (true)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ast/merge/git/local_benchmark.rb', line 55

def validate!
  require_keys(document, %w[schema_version kind id version extends provenance profiles capability_map
                            selection 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_cases!
  validate_summary!
  true
end