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
EXECUTABLE_OPERATIONS =
%w[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]])
{
  '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)


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

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)


42
43
44
# File 'lib/ast/merge/git/local_benchmark.rb', line 42

def corpus_digest
  @corpus_digest
end

#documentHash[String, untyped] (readonly)

Returns the value of attribute document.

Returns:

  • (Hash[String, untyped])


42
43
44
# File 'lib/ast/merge/git/local_benchmark.rb', line 42

def document
  @document
end

#pathPathname? (readonly)

Returns the value of attribute path.

Returns:

  • (Pathname, nil)


42
43
44
# File 'lib/ast/merge/git/local_benchmark.rb', line 42

def path
  @path
end

Class Method Details

.load(path) ⇒ LocalBenchmark

Parameters:

  • path (String, Pathname)

Returns:



44
45
46
47
48
49
50
51
# File 'lib/ast/merge/git/local_benchmark.rb', line 44

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


132
133
134
# File 'lib/ast/merge/git/local_benchmark.rb', line 132

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


75
76
77
78
# File 'lib/ast/merge/git/local_benchmark.rb', line 75

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


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
124
125
126
127
128
129
130
# File 'lib/ast/merge/git/local_benchmark.rb', line 80

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

#validate!true

Returns:

  • (true)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ast/merge/git/local_benchmark.rb', line 59

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