condition_coverage 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ module ConditionCoverage
2
+ # The lines of one file's page, each with the status of the
3
+ # conditions on it.
4
+ module SourceLines
5
+ # [{number:, text:, status:, span:}] for each line of
6
+ # source. A line in a site's span shares one button with
7
+ # the span's other lines; status and span are nil on a line
8
+ # with no condition.
9
+ def self.of(source, sites, operands)
10
+ marks = marks(source, sites, operands)
11
+ source.lines.each_with_index.map do |text, index|
12
+ number = index + 1
13
+ mark = marks.fetch(number, {})
14
+ {
15
+ number:, text: text.chomp,
16
+ status: mark[:status], span: mark[:span]
17
+ }
18
+ end
19
+ end
20
+
21
+ # { line number => {status:, span:} } for each line a site
22
+ # covers, every line of a merged span sharing that span.
23
+ def self.marks(source, sites, operands)
24
+ blocks = merged(spans_of(source, sites, operands))
25
+ blocks.each_with_object({}) do |(span, status), marks|
26
+ span.each { |number| marks[number] = { status:, span: } }
27
+ end
28
+ end
29
+
30
+ # [span, status] for each site: from the line it starts on
31
+ # to the line its last operand ends on.
32
+ def self.spans_of(source, sites, operands)
33
+ sites.map do |site, condition|
34
+ last = condition.leaves.last
35
+ span = line_of(site)..end_line(source, last)
36
+ [span, status_of(operands.fetch(site))]
37
+ end
38
+ end
39
+
40
+ # The spans merged wherever they overlap, in line order. A
41
+ # merged span has :gaps if any of its sites has, so a
42
+ # covered site never hides another's gaps.
43
+ def self.merged(spans)
44
+ sorted = spans.sort_by { |span, _| span.begin }
45
+ sorted.each_with_object([]) do |(span, status), blocks|
46
+ last_span, last_status = blocks.last
47
+ if last_span && last_span.end >= span.begin
48
+ joined = last_span.begin..[last_span.end, span.end].max
49
+ blocks[-1] = [joined, worse(last_status, status)]
50
+ else
51
+ blocks << [span, status]
52
+ end
53
+ end
54
+ end
55
+
56
+ # :gaps if either status is :gaps, else :covered.
57
+ def self.worse(one, other)
58
+ [one, other].include?(:gaps) ? :gaps : :covered
59
+ end
60
+
61
+ # :covered when every operand was seen both ways, else :gaps.
62
+ def self.status_of(counts)
63
+ counts.all? { |operand| seen_both?(operand) } ? :covered : :gaps
64
+ end
65
+
66
+ # True when the operand was seen both true and false.
67
+ def self.seen_both?(counts)
68
+ counts[:truthy].positive? && counts[:falsy].positive?
69
+ end
70
+
71
+ # The line a site starts on, from its path:line:column name.
72
+ def self.line_of(site)
73
+ site[/:(\d+):\d+\z/, 1].to_i
74
+ end
75
+
76
+ # The line of source on which byte range ends.
77
+ def self.end_line(source, range)
78
+ source.byteslice(0, range.end).count("\n") + 1
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,32 @@
1
+ module ConditionCoverage
2
+ # The per-file numbers the HTML index page shows.
3
+ module Summary
4
+ # One row per file that has sites in result, in path order.
5
+ def self.files(result)
6
+ by_path = result.operands.group_by { |site, _| path_of(site) }
7
+ by_path.sort.map { |path, sites| row(path, sites) }
8
+ end
9
+
10
+ # The summary row of one file's sites.
11
+ def self.row(path, sites)
12
+ operands = sites.flat_map { |_, counts| counts }
13
+ outcomes = operands.size * 2
14
+ seen = operands.sum { |counts| seen_in(counts) }
15
+ {
16
+ path:, sites: sites.size, seen:, outcomes:,
17
+ percent: (100.0 * seen / outcomes).round,
18
+ missing: outcomes - seen
19
+ }
20
+ end
21
+
22
+ # How many of an operand's two outcomes were seen.
23
+ def self.seen_in(counts)
24
+ [counts[:truthy], counts[:falsy]].count(&:positive?)
25
+ end
26
+
27
+ # The file a site is in: its name minus :line:column.
28
+ def self.path_of(site)
29
+ site.sub(/:\d+:\d+\z/, '')
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module ConditionCoverage
2
+ VERSION = '0.0.2'.freeze
3
+ end
@@ -1,3 +1,2 @@
1
- module ConditionCoverage
2
- VERSION = '0.0.1'
3
- end
1
+ require_relative 'condition_coverage/version'
2
+ require_relative 'condition_coverage/kernel'
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: condition_coverage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Jagger
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2026-10-31 00:00:00.000000000 Z
@@ -24,8 +24,9 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
- description: Placeholder release outlining the idea; see the README.
28
- email:
27
+ description: Records whether each operand of every &&, ||, and and or condition was
28
+ seen both true and false, and reports the gaps in the terminal and as HTML.
29
+ email:
29
30
  executables: []
30
31
  extensions: []
31
32
  extra_rdoc_files: []
@@ -33,11 +34,35 @@ files:
33
34
  - LICENSE.md
34
35
  - README.md
35
36
  - lib/condition_coverage.rb
37
+ - lib/condition_coverage/condition.rb
38
+ - lib/condition_coverage/configuration.rb
39
+ - lib/condition_coverage/gaps.rb
40
+ - lib/condition_coverage/html/file.html
41
+ - lib/condition_coverage/html/index.html
42
+ - lib/condition_coverage/html/sort.js
43
+ - lib/condition_coverage/html/style.css
44
+ - lib/condition_coverage/html/toggle.js
45
+ - lib/condition_coverage/html_file.rb
46
+ - lib/condition_coverage/html_index.rb
47
+ - lib/condition_coverage/html_report.rb
48
+ - lib/condition_coverage/html_template.rb
49
+ - lib/condition_coverage/json_writer.rb
50
+ - lib/condition_coverage/kernel.rb
51
+ - lib/condition_coverage/manifest.rb
52
+ - lib/condition_coverage/recorder.rb
53
+ - lib/condition_coverage/result.rb
54
+ - lib/condition_coverage/rewriter.rb
55
+ - lib/condition_coverage/site.rb
56
+ - lib/condition_coverage/site_tree.rb
57
+ - lib/condition_coverage/source_lines.rb
58
+ - lib/condition_coverage/summary.rb
59
+ - lib/condition_coverage/version.rb
36
60
  homepage: https://github.com/cyber-dojo/condition_coverage
37
61
  licenses:
38
62
  - BSD-2-Clause
39
- metadata: {}
40
- post_install_message:
63
+ metadata:
64
+ rubygems_mfa_required: 'true'
65
+ post_install_message:
41
66
  rdoc_options: []
42
67
  require_paths:
43
68
  - lib
@@ -45,15 +70,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
70
  requirements:
46
71
  - - ">="
47
72
  - !ruby/object:Gem::Version
48
- version: '0'
73
+ version: '3.2'
49
74
  required_rubygems_version: !ruby/object:Gem::Requirement
50
75
  requirements:
51
76
  - - ">="
52
77
  - !ruby/object:Gem::Version
53
78
  version: '0'
54
79
  requirements: []
55
- rubygems_version: 3.0.3.1
56
- signing_key:
80
+ rubygems_version: 3.5.22
81
+ signing_key:
57
82
  specification_version: 4
58
83
  summary: Condition coverage for Ruby, alongside SimpleCov branch coverage
59
84
  test_files: []