path_coverage 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +287 -78
- data/lib/path_coverage/calls.rb +54 -15
- data/lib/path_coverage/condition.rb +25 -2
- data/lib/path_coverage/configuration.rb +44 -7
- data/lib/path_coverage/configuration_paths.rb +56 -0
- data/lib/path_coverage/console_summary.rb +77 -0
- data/lib/path_coverage/gaps.rb +25 -9
- data/lib/path_coverage/html/index.html +3 -17
- data/lib/path_coverage/html/side.js +21 -2
- data/lib/path_coverage/html/sort.js +24 -21
- data/lib/path_coverage/html/style.css +28 -1
- data/lib/path_coverage/html/tabs.js +15 -0
- data/lib/path_coverage/html_index.rb +86 -31
- data/lib/path_coverage/html_index_head.rb +49 -0
- data/lib/path_coverage/html_report.rb +30 -13
- data/lib/path_coverage/html_tabs.rb +26 -0
- data/lib/path_coverage/html_tree.rb +41 -20
- data/lib/path_coverage/json_reader.rb +94 -0
- data/lib/path_coverage/json_writer.rb +41 -9
- data/lib/path_coverage/kernel.rb +42 -20
- data/lib/path_coverage/kinds/base.rb +71 -0
- data/lib/path_coverage/kinds/block.rb +136 -0
- data/lib/path_coverage/kinds/case.rb +89 -0
- data/lib/path_coverage/kinds/chain.rb +45 -0
- data/lib/path_coverage/kinds/choice.rb +68 -0
- data/lib/path_coverage/kinds/contains.rb +111 -0
- data/lib/path_coverage/kinds/safe.rb +151 -0
- data/lib/path_coverage/kinds/write.rb +54 -0
- data/lib/path_coverage/kinds.rb +37 -0
- data/lib/path_coverage/manifest.rb +58 -19
- data/lib/path_coverage/manifest_blocks.rb +49 -10
- data/lib/path_coverage/manifest_chains.rb +76 -0
- data/lib/path_coverage/manifest_choices.rb +30 -17
- data/lib/path_coverage/manifest_folding.rb +91 -0
- data/lib/path_coverage/manifest_ignoring.rb +35 -0
- data/lib/path_coverage/manifest_writes.rb +67 -0
- data/lib/path_coverage/merge.rb +19 -3
- data/lib/path_coverage/post.rb +8 -8
- data/lib/path_coverage/recorder.rb +9 -49
- data/lib/path_coverage/recorder_blocks.rb +68 -0
- data/lib/path_coverage/recorder_cases.rb +23 -0
- data/lib/path_coverage/recorder_chains.rb +40 -0
- data/lib/path_coverage/recorder_writes.rb +26 -0
- data/lib/path_coverage/render.rb +58 -46
- data/lib/path_coverage/result.rb +19 -0
- data/lib/path_coverage/rewriter.rb +58 -77
- data/lib/path_coverage/rewriter_nested.rb +62 -0
- data/lib/path_coverage/site_paths.rb +9 -58
- data/lib/path_coverage/site_tree.rb +12 -57
- data/lib/path_coverage/summary.rb +32 -4
- data/lib/path_coverage/totals.rb +1 -1
- data/lib/path_coverage/version.rb +1 -1
- metadata +30 -6
|
@@ -1,78 +1,29 @@
|
|
|
1
1
|
require_relative 'condition'
|
|
2
|
+
require_relative 'kinds'
|
|
2
3
|
|
|
3
4
|
module PathCoverage
|
|
4
5
|
# The paths an evaluation of a site can take, worked out
|
|
5
6
|
# from its condition alone: the outcome of each leaf, in leaf
|
|
6
7
|
# order, nil for a leaf that evaluation never reaches.
|
|
7
8
|
module SitePaths
|
|
8
|
-
# Every possible path through site's condition, true
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every possible path through site's condition, true before
|
|
10
|
+
# false, as its kind gives the ways a site of it can be
|
|
11
|
+
# evaluated.
|
|
11
12
|
def self.possible(site)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
walk(site, site.fixed).map do |outcomes, _value|
|
|
13
|
+
ways = Kinds.of(site.operator).root_ways(site, site.fixed)
|
|
14
|
+
ways.map do |outcomes, _value|
|
|
16
15
|
site.leaves.map { |range| outcomes[range] }
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
# The outcome of an operand on which each operator goes on
|
|
21
|
-
# to its next operand; any other ends the chain there. A
|
|
22
|
-
# when's patterns, and a case's whens, stop at a match.
|
|
23
|
-
CONTINUES_ON = {
|
|
24
|
-
'&&' => true, 'and' => true, '||' => false, 'or' => false,
|
|
25
|
-
'when' => false, 'case' => false
|
|
26
|
-
}.freeze
|
|
27
|
-
|
|
28
19
|
# [[{ leaf range => outcome }, value], ...] for each way
|
|
29
20
|
# node can be evaluated, value being the outcome of node as
|
|
30
|
-
# a whole; fixed is the site's
|
|
31
|
-
#
|
|
21
|
+
# a whole, as its kind gives them; fixed is the site's
|
|
22
|
+
# { leaf range => its one outcome }.
|
|
32
23
|
def self.walk(node, fixed)
|
|
33
24
|
return leaf(node, fixed) unless node.is_a?(Condition)
|
|
34
|
-
return ternary(node, fixed) if CHOICES.include?(node.operator)
|
|
35
|
-
|
|
36
|
-
if node.operator == 'case'
|
|
37
|
-
whens = node.operands.filter_map(&:body)
|
|
38
|
-
return chain(node.with(operands: whens), fixed)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
chain(node, fixed)
|
|
42
|
-
end
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
# way of its first operand, going on to the next while each
|
|
46
|
-
# outcome is the one its operator goes on with.
|
|
47
|
-
def self.chain(node, fixed)
|
|
48
|
-
first, *rest = node.operands
|
|
49
|
-
goes_on = CONTINUES_ON.fetch(node.operator)
|
|
50
|
-
rest.reduce(walk(first, fixed)) do |ways, operand|
|
|
51
|
-
ways.flat_map do |outcomes, value|
|
|
52
|
-
next [[outcomes, value]] unless value == goes_on
|
|
53
|
-
|
|
54
|
-
walk(operand, fixed).map do |more, after|
|
|
55
|
-
[outcomes.merge(more), after]
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# The ways a ternary, an if or an unless can be evaluated:
|
|
62
|
-
# each way of its predicate, then each way of the condition
|
|
63
|
-
# the arm it picks is, if that arm is one; one with no else
|
|
64
|
-
# arm picks none. Its value is that of no choice, so is nil.
|
|
65
|
-
def self.ternary(node, fixed)
|
|
66
|
-
predicate, then_arm, else_arm = node.operands
|
|
67
|
-
then_on = THEN_ON[node.operator]
|
|
68
|
-
walk(predicate, fixed).flat_map do |outcomes, value|
|
|
69
|
-
body = (value == then_on ? then_arm : else_arm)&.body
|
|
70
|
-
next [[outcomes, nil]] if body.nil?
|
|
71
|
-
|
|
72
|
-
walk(body, fixed).map do |more, _|
|
|
73
|
-
[outcomes.merge(more), nil]
|
|
74
|
-
end
|
|
75
|
-
end
|
|
26
|
+
Kinds.of(node.operator).ways(node, fixed)
|
|
76
27
|
end
|
|
77
28
|
|
|
78
29
|
# The ways a leaf can be evaluated: true, then false, or
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative 'condition'
|
|
2
|
+
require_relative 'kinds'
|
|
2
3
|
|
|
3
4
|
module PathCoverage
|
|
4
5
|
# The tree shown under a site's line on its file's page: a
|
|
@@ -21,27 +22,25 @@ module PathCoverage
|
|
|
21
22
|
tree.merge(counts: columns.map { |taken| taken[:count] })
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
# The tree of condition,
|
|
25
|
-
# leaves, so leaves pair with path
|
|
25
|
+
# The tree of condition, as its kind builds it, each leaf
|
|
26
|
+
# taking the next index of leaves, so leaves pair with path
|
|
27
|
+
# entries left to right.
|
|
26
28
|
def self.node(source, condition, outcomes, leaves)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
operands = operands_of(source, condition, outcomes, leaves)
|
|
32
|
-
arms(source, condition, operands, outcomes, leaves)
|
|
29
|
+
kind = Kinds.of(condition.operator)
|
|
30
|
+
kind.node(source, condition, outcomes, leaves)
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
# condition's operands' nodes, leaves and nested conditions
|
|
36
34
|
# built in order, its arms left for arms to build after.
|
|
37
35
|
def self.operands_of(source, condition, outcomes, leaves)
|
|
38
|
-
condition.operands.map do |operand|
|
|
36
|
+
nodes = condition.operands.map do |operand|
|
|
39
37
|
case operand
|
|
40
38
|
when Condition then node(source, operand, outcomes, leaves)
|
|
41
39
|
when Arm then operand
|
|
42
40
|
else leaf(source, operand, outcomes, leaves.next)
|
|
43
41
|
end
|
|
44
42
|
end
|
|
43
|
+
Kinds.of(condition.operator).texted(source, condition, nodes)
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
# condition's node from its operands' nodes, its ternary
|
|
@@ -55,36 +54,14 @@ module PathCoverage
|
|
|
55
54
|
decided = [predicate[:cells], THEN_ON[condition.operator]]
|
|
56
55
|
arm(source, operand, decided, outcomes, leaves)
|
|
57
56
|
end
|
|
58
|
-
|
|
57
|
+
operator = condition.operator
|
|
58
|
+
cells = Kinds.of(operator).value(operator, operands)
|
|
59
59
|
{ operator: condition.operator, cells:, operands: }
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
# A case's node from its arms, built in order so its whens'
|
|
63
|
-
# patterns pair with path entries left to right: a when is
|
|
64
|
-
# taken on each path one of its patterns matched, and the
|
|
65
|
-
# else on each none did. A case's value is that of no
|
|
66
|
-
# choice, so is nil.
|
|
67
|
-
def self.case_arms(source, arms, outcomes, leaves)
|
|
68
|
-
matched = []
|
|
69
|
-
operands = arms.map do |arm|
|
|
70
|
-
body = arm.body && node(source, arm.body, outcomes, leaves)
|
|
71
|
-
matched << body[:cells].map { |cell| cell == true } if body
|
|
72
|
-
cells = body ? matched.last : unmatched(matched, outcomes)
|
|
73
|
-
{ arm: arm.kind, text: text_of(source, arm), cells:, body: }
|
|
74
|
-
end
|
|
75
|
-
{ operator: 'case', cells: Array.new(outcomes.size), operands: }
|
|
76
|
-
end
|
|
77
|
-
|
|
78
62
|
# An arm's text, nil for one with no statements.
|
|
79
63
|
def self.text_of(source, arm)
|
|
80
|
-
arm.extent
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Whether each column matched none of its whens' patterns.
|
|
84
|
-
def self.unmatched(matched, outcomes)
|
|
85
|
-
outcomes.each_index.map do |column|
|
|
86
|
-
matched.none? { |taken| taken[column] }
|
|
87
|
-
end
|
|
64
|
+
arm.extent ? source.byteslice(arm.extent) : nil
|
|
88
65
|
end
|
|
89
66
|
|
|
90
67
|
# One arm of a ternary, an if or an unless: its text, nil
|
|
@@ -94,35 +71,13 @@ module PathCoverage
|
|
|
94
71
|
# condition.
|
|
95
72
|
def self.arm(source, arm, (decided, then_on), outcomes, leaves)
|
|
96
73
|
picked = arm.kind == :then ? then_on : !then_on
|
|
97
|
-
body = arm.body
|
|
74
|
+
body = arm.body ? node(source, arm.body, outcomes, leaves) : nil
|
|
98
75
|
{
|
|
99
76
|
arm: arm.kind, text: text_of(source, arm),
|
|
100
77
|
cells: decided.map { |outcome| outcome == picked }, body:
|
|
101
78
|
}
|
|
102
79
|
end
|
|
103
80
|
|
|
104
|
-
# A condition's value in each column: a ternary's or an
|
|
105
|
-
# if's is its taken arm's body's, nil for a plain arm; a
|
|
106
|
-
# chain's is that of the last operand it reached.
|
|
107
|
-
def self.value(operator, operands)
|
|
108
|
-
if CHOICES.include?(operator)
|
|
109
|
-
_, *arms = operands
|
|
110
|
-
arms.map { |arm| arm[:body]&.fetch(:cells) }
|
|
111
|
-
.then { |bodies| ternary_value(arms, bodies) }
|
|
112
|
-
else
|
|
113
|
-
operands.map { |operand| operand[:cells] }
|
|
114
|
-
.transpose.map { |column| column.compact.last }
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Each column's value from the arm it took.
|
|
119
|
-
def self.ternary_value(arms, bodies)
|
|
120
|
-
arms.first[:cells].each_index.map do |column|
|
|
121
|
-
taken = arms.index { |arm| arm[:cells][column] }
|
|
122
|
-
taken && bodies[taken]&.fetch(column)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
81
|
# One leaf's text and its outcome in each column.
|
|
127
82
|
def self.leaf(source, range, outcomes, index)
|
|
128
83
|
{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require_relative 'configuration'
|
|
1
2
|
require_relative 'totals'
|
|
2
3
|
|
|
3
4
|
module PathCoverage
|
|
@@ -16,7 +17,7 @@ module PathCoverage
|
|
|
16
17
|
{
|
|
17
18
|
path:, sites: sites.size,
|
|
18
19
|
decisions: Totals.decisions(sites.to_h),
|
|
19
|
-
methods: paths
|
|
20
|
+
methods: paths ? methods_of(path, methods, paths) : nil
|
|
20
21
|
}
|
|
21
22
|
end
|
|
22
23
|
end
|
|
@@ -35,13 +36,40 @@ module PathCoverage
|
|
|
35
36
|
{
|
|
36
37
|
sites: rows.sum { |row| row[:sites] },
|
|
37
38
|
decisions: add(rows.map { |row| row[:decisions] }),
|
|
38
|
-
methods: methods.first
|
|
39
|
+
methods: methods.first ? add(methods) : nil
|
|
39
40
|
}
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
# [
|
|
43
|
+
# [name, rows, total] for each of groups, { name => [glob,
|
|
44
|
+
# ...] }, in order: the rows whose path matches any of its
|
|
45
|
+
# globs, and their total. Then Ungrouped, the rows no group
|
|
46
|
+
# matches, when there are any.
|
|
47
|
+
def self.groups(rows, groups)
|
|
48
|
+
grouped = groups.map do |name, globs|
|
|
49
|
+
own = rows.select { |row| matches?(row[:path], globs) }
|
|
50
|
+
[name, own, total(own)]
|
|
51
|
+
end
|
|
52
|
+
every = groups.values.flatten
|
|
53
|
+
rest = rows.reject { |row| matches?(row[:path], every) }
|
|
54
|
+
return grouped if rest.empty?
|
|
55
|
+
|
|
56
|
+
grouped << ['Ungrouped', rest, total(rest)]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# True when path matches any of globs.
|
|
60
|
+
def self.matches?(path, globs)
|
|
61
|
+
globs.any? do |glob|
|
|
62
|
+
File.fnmatch?(glob, path, Configuration::GLOB_FLAGS)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# [taken, possible] pairs added together, eg [[1, 3], [2, 4]]
|
|
67
|
+
# gives [3, 7]; no pairs, as for a group no file is in, gives
|
|
68
|
+
# [0, 0].
|
|
43
69
|
def self.add(pairs)
|
|
44
|
-
pairs.
|
|
70
|
+
pairs.reduce([0, 0]) do |(taken, possible), (more, of)|
|
|
71
|
+
[taken + more, possible + of]
|
|
72
|
+
end
|
|
45
73
|
end
|
|
46
74
|
|
|
47
75
|
# The file a site is in: its name minus :line:column.
|
data/lib/path_coverage/totals.rb
CHANGED
|
@@ -13,7 +13,7 @@ module PathCoverage
|
|
|
13
13
|
[every.count { |each| each[:count].positive? }, every.size]
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# Paths
|
|
16
|
+
# Paths within each method, through its decisions: methods, as
|
|
17
17
|
# PathManifest.methods gives them, each with every path
|
|
18
18
|
# through its decisions; paths, as the recorder counts
|
|
19
19
|
# them, holds the ones taken.
|
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: path_coverage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jon Jagger
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2026-11-03 00:00:00.000000000 Z
|
|
@@ -26,7 +26,7 @@ dependencies:
|
|
|
26
26
|
version: '1.0'
|
|
27
27
|
description: Records which paths each condition and each method took, and reports
|
|
28
28
|
the paths never taken in the terminal and as HTML.
|
|
29
|
-
email:
|
|
29
|
+
email:
|
|
30
30
|
executables:
|
|
31
31
|
- path_coverage_collector
|
|
32
32
|
extensions: []
|
|
@@ -41,35 +41,59 @@ files:
|
|
|
41
41
|
- lib/path_coverage/collector.rb
|
|
42
42
|
- lib/path_coverage/condition.rb
|
|
43
43
|
- lib/path_coverage/configuration.rb
|
|
44
|
+
- lib/path_coverage/configuration_paths.rb
|
|
45
|
+
- lib/path_coverage/console_summary.rb
|
|
44
46
|
- lib/path_coverage/gaps.rb
|
|
45
47
|
- lib/path_coverage/html/file.html
|
|
46
48
|
- lib/path_coverage/html/index.html
|
|
47
49
|
- lib/path_coverage/html/side.js
|
|
48
50
|
- lib/path_coverage/html/sort.js
|
|
49
51
|
- lib/path_coverage/html/style.css
|
|
52
|
+
- lib/path_coverage/html/tabs.js
|
|
50
53
|
- lib/path_coverage/html/toggle.js
|
|
51
54
|
- lib/path_coverage/html_file.rb
|
|
52
55
|
- lib/path_coverage/html_index.rb
|
|
56
|
+
- lib/path_coverage/html_index_head.rb
|
|
53
57
|
- lib/path_coverage/html_nav.rb
|
|
54
58
|
- lib/path_coverage/html_paths.rb
|
|
55
59
|
- lib/path_coverage/html_report.rb
|
|
60
|
+
- lib/path_coverage/html_tabs.rb
|
|
56
61
|
- lib/path_coverage/html_template.rb
|
|
57
62
|
- lib/path_coverage/html_tree.rb
|
|
63
|
+
- lib/path_coverage/json_reader.rb
|
|
58
64
|
- lib/path_coverage/json_writer.rb
|
|
59
65
|
- lib/path_coverage/kernel.rb
|
|
66
|
+
- lib/path_coverage/kinds.rb
|
|
67
|
+
- lib/path_coverage/kinds/base.rb
|
|
68
|
+
- lib/path_coverage/kinds/block.rb
|
|
69
|
+
- lib/path_coverage/kinds/case.rb
|
|
70
|
+
- lib/path_coverage/kinds/chain.rb
|
|
71
|
+
- lib/path_coverage/kinds/choice.rb
|
|
72
|
+
- lib/path_coverage/kinds/contains.rb
|
|
73
|
+
- lib/path_coverage/kinds/safe.rb
|
|
74
|
+
- lib/path_coverage/kinds/write.rb
|
|
60
75
|
- lib/path_coverage/manifest.rb
|
|
61
76
|
- lib/path_coverage/manifest_blocks.rb
|
|
62
77
|
- lib/path_coverage/manifest_case.rb
|
|
78
|
+
- lib/path_coverage/manifest_chains.rb
|
|
63
79
|
- lib/path_coverage/manifest_choices.rb
|
|
80
|
+
- lib/path_coverage/manifest_folding.rb
|
|
81
|
+
- lib/path_coverage/manifest_ignoring.rb
|
|
82
|
+
- lib/path_coverage/manifest_writes.rb
|
|
64
83
|
- lib/path_coverage/merge.rb
|
|
65
84
|
- lib/path_coverage/path_gaps.rb
|
|
66
85
|
- lib/path_coverage/path_manifest.rb
|
|
67
86
|
- lib/path_coverage/paths_report.rb
|
|
68
87
|
- lib/path_coverage/post.rb
|
|
69
88
|
- lib/path_coverage/recorder.rb
|
|
89
|
+
- lib/path_coverage/recorder_blocks.rb
|
|
90
|
+
- lib/path_coverage/recorder_cases.rb
|
|
91
|
+
- lib/path_coverage/recorder_chains.rb
|
|
92
|
+
- lib/path_coverage/recorder_writes.rb
|
|
70
93
|
- lib/path_coverage/render.rb
|
|
71
94
|
- lib/path_coverage/result.rb
|
|
72
95
|
- lib/path_coverage/rewriter.rb
|
|
96
|
+
- lib/path_coverage/rewriter_nested.rb
|
|
73
97
|
- lib/path_coverage/site.rb
|
|
74
98
|
- lib/path_coverage/site_paths.rb
|
|
75
99
|
- lib/path_coverage/site_tree.rb
|
|
@@ -82,7 +106,7 @@ licenses:
|
|
|
82
106
|
- BSD-2-Clause
|
|
83
107
|
metadata:
|
|
84
108
|
rubygems_mfa_required: 'true'
|
|
85
|
-
post_install_message:
|
|
109
|
+
post_install_message:
|
|
86
110
|
rdoc_options: []
|
|
87
111
|
require_paths:
|
|
88
112
|
- lib
|
|
@@ -97,8 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
97
121
|
- !ruby/object:Gem::Version
|
|
98
122
|
version: '0'
|
|
99
123
|
requirements: []
|
|
100
|
-
rubygems_version: 3.
|
|
101
|
-
signing_key:
|
|
124
|
+
rubygems_version: 3.0.3.1
|
|
125
|
+
signing_key:
|
|
102
126
|
specification_version: 4
|
|
103
127
|
summary: Path coverage for Ruby, alongside SimpleCov branch coverage
|
|
104
128
|
test_files: []
|