Class: Ibex::Coverage::Report

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/coverage/report.rb

Overview

Versioned, mergeable runtime coverage result.

Constant Summary collapse

IDENTIFIER =

: String

"runtime-coverage"
SCHEMA_VERSION =

: Integer

1
MAX_DOCUMENT_BYTES =

: Integer

16_777_216
MAX_TOTAL =

: Integer

1_000_000
MAX_COUNT =

: Integer

9_223_372_036_854_775_807
ROOT_KEYS =
%w[events grammar_digest ibex_coverage production_hits schema_version sessions state_hits
table_format_version totals].freeze
TOTAL_KEYS =

: Array

%w[productions states].freeze
HIT_KEYS =

: Array

%w[count id].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(grammar_digest:, table_format_version:, state_count:, production_count:, sessions:, event_count:, state_hits:, production_hits:) ⇒ Report

rubocop:disable Layout/LineLength

Raises:

  • (ArgumentError)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ibex/coverage/report.rb', line 32

def initialize(grammar_digest:, table_format_version:, state_count:, production_count:, sessions:, event_count:,
               state_hits:, production_hits:)
  @grammar_digest = validate_digest(grammar_digest)
  @table_format_version = positive_integer(table_format_version, "table_format_version")
  @state_count = total_integer(state_count, "state total", minimum: 1)
  @production_count = total_integer(production_count, "production total", minimum: 0)
  @sessions = positive_integer(sessions, "sessions")
  @event_count = positive_integer(event_count, "events")
  raise ArgumentError, "events must be at least sessions" if @event_count < @sessions

  @state_hits = validate_hits(state_hits, @state_count, "state").freeze
  @production_hits = validate_hits(production_hits, @production_count, "production").freeze
  validate_event_bounds
  freeze
end

Instance Attribute Details

#event_countObject (readonly)

: Integer



26
27
28
# File 'lib/ibex/coverage/report.rb', line 26

def event_count
  @event_count
end

#grammar_digestObject (readonly)

: Array



21
22
23
# File 'lib/ibex/coverage/report.rb', line 21

def grammar_digest
  @grammar_digest
end

#production_countObject (readonly)

: Integer



24
25
26
# File 'lib/ibex/coverage/report.rb', line 24

def production_count
  @production_count
end

#production_hitsObject (readonly)

: Hash[Integer, Integer]



28
29
30
# File 'lib/ibex/coverage/report.rb', line 28

def production_hits
  @production_hits
end

#sessionsObject (readonly)

: Integer



25
26
27
# File 'lib/ibex/coverage/report.rb', line 25

def sessions
  @sessions
end

#state_countObject (readonly)

: Integer



23
24
25
# File 'lib/ibex/coverage/report.rb', line 23

def state_count
  @state_count
end

#state_hitsObject (readonly)

: Hash[Integer, Integer]



27
28
29
# File 'lib/ibex/coverage/report.rb', line 27

def state_hits
  @state_hits
end

#table_format_versionObject (readonly)

: Integer



22
23
24
# File 'lib/ibex/coverage/report.rb', line 22

def table_format_version
  @table_format_version
end

Class Method Details

.from_h(value, source:) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/ibex/coverage/report.rb', line 91

def self.from_h(value, source:)
  document = string_hash(value, source, "coverage document")
  invalid(source, "coverage object has unknown or missing fields") unless document.keys.sort == ROOT_KEYS
  valid_schema = document["ibex_coverage"] == IDENTIFIER &&
                 document["schema_version"] == SCHEMA_VERSION
  invalid(source, "unsupported coverage schema") unless valid_schema
  totals = string_hash(document["totals"], source, "coverage totals")
  invalid(source, "coverage totals have unknown or missing fields") unless totals.keys.sort == TOTAL_KEYS

  state_count = totals["states"]
  production_count = totals["productions"]
  new(
    grammar_digest: document["grammar_digest"],
    table_format_version: document["table_format_version"],
    state_count: state_count,
    production_count: production_count,
    sessions: document["sessions"],
    event_count: document["events"],
    state_hits: parse_hits(document["state_hits"], state_count, source, "state"),
    production_hits: parse_hits(document["production_hits"], production_count, source, "production")
  )
rescue ArgumentError => e
  raise Ibex::Error, "#{source}:1:1: #{e.message}"
end

.load_file(path) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/ibex/coverage/report.rb', line 70

def self.load_file(path)
  size = File.size(path)
  if size > MAX_DOCUMENT_BYTES
    raise Ibex::Error, "#{path}:1:1: coverage document exceeds #{MAX_DOCUMENT_BYTES} bytes"
  end

  source = File.binread(path)
  if source.bytesize > MAX_DOCUMENT_BYTES
    raise Ibex::Error, "#{path}:1:1: coverage document exceeds #{MAX_DOCUMENT_BYTES} bytes"
  end

  source.force_encoding(Encoding::UTF_8)
  raise Ibex::Error, "#{path}:1:1: coverage document is not valid UTF-8" unless source.valid_encoding?

  value = JSON.parse(source, max_nesting: 16, allow_nan: false)
  from_h(value, source: path)
rescue JSON::ParserError => e
  raise Ibex::Error, "#{path}:1:1: invalid coverage JSON: #{e.message}"
end

.merge(reports) ⇒ Object

Raises:

  • (ArgumentError)


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/ibex/coverage/report.rb', line 117

def self.merge(reports)
  raise ArgumentError, "at least one coverage report is required" if reports.empty?

  first = reports.first
  reports.drop(1).each { |report| ensure_compatible(first, report) }
  new(
    grammar_digest: first.grammar_digest,
    table_format_version: first.table_format_version,
    state_count: first.state_count,
    production_count: first.production_count,
    sessions: checked_sum(reports.map(&:sessions), "sessions"),
    event_count: checked_sum(reports.map(&:event_count), "events"),
    state_hits: merge_hits(reports.map(&:state_hits), "state"),
    production_hits: merge_hits(reports.map(&:production_hits), "production")
  )
end

Instance Method Details

#to_hObject



50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/ibex/coverage/report.rb', line 50

def to_h
  {
    "ibex_coverage" => IDENTIFIER,
    "schema_version" => SCHEMA_VERSION,
    "grammar_digest" => @grammar_digest,
    "table_format_version" => @table_format_version,
    "totals" => { "states" => @state_count, "productions" => @production_count },
    "sessions" => @sessions,
    "events" => @event_count,
    "state_hits" => hit_documents(@state_hits),
    "production_hits" => hit_documents(@production_hits)
  }
end

#to_jsonObject



65
66
67
# File 'lib/ibex/coverage/report.rb', line 65

def to_json(*)
  "#{JSON.pretty_generate(to_h)}\n"
end