condition_coverage 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a61c323a92442b231f61a10ea7ee622ee5e3b957f9818819e56aefda2ed75efd
4
- data.tar.gz: eba0b9b94f2ba0756f0549d9675a4d3cb4c548df4854114df6e76e7ce09cdbeb
3
+ metadata.gz: f113d970620b81b385c0ac7f1ce0e89251574bf8f14498be64b460decb35d315
4
+ data.tar.gz: 5f081efffd90f7d0c216d9e5cfbdb9746e2d1a4dee2384a54ad5b5f8b31cbc1f
5
5
  SHA512:
6
- metadata.gz: f84973aba8aa9323ba03323f5fabe1b99feda591d0973a715e27f792a61971c50fb87b90680db5245451f16a90180dcae7272a8bdc33fd25998429d63860cd45
7
- data.tar.gz: b992c871ed4f1bd658d828fec3da583199a4ddf3a44c26f0cb79b142f57f74e294a5f9c50e672780fb4f213986163f9c5b0a64e03903e462aebaf8c7a78e07ae
6
+ metadata.gz: 934738b27336db9ff08212d39601f1a8b64c99bcf2a989ff07369334ecdde3bf8d817963c9800ac010351589203e04dc06be14738a3eae759eae7a61d736946d
7
+ data.tar.gz: c6b92d448eeb7b62623de4c8cc6b135b13e1bea82dcb6a62555f590331fd6e53d5992b0998171eeb145ca032ccfbbaf21e6513c1448ac3497a5ef88bd582d04b
data/README.md CHANGED
@@ -43,13 +43,47 @@ end
43
43
  A misspelt setting or an unknown reporter is refused by name, with the list
44
44
  of the ones there are. Calling `start` again replaces the configuration.
45
45
 
46
+ A run whose code cannot be changed, such as a script in a `docker build`
47
+ step, can be covered from its environment alone:
48
+
49
+ ```sh
50
+ RUBYOPT=-rcondition_coverage/auto
51
+ CONDITION_COVERAGE_ROOT=/app # root
52
+ CONDITION_COVERAGE_COVER=src/**/*.rb,lib/*.rb # cover, comma-separated
53
+ CONDITION_COVERAGE_DIR=/tmp/condition # coverage_dir
54
+ CONDITION_COVERAGE_COLLECTOR=http://host.docker.internal:4570/runs
55
+ ```
56
+
57
+ This starts with `report :post`. With no `CONDITION_COVERAGE_COLLECTOR` it
58
+ starts nothing and prints nothing, so an image can set `RUBYOPT` for every
59
+ run and measure only those given a collector.
60
+
61
+ The gem's `condition_coverage_collector` command is that collector. It
62
+ saves each report POSTed to it as a numbered file, and once every run is
63
+ done they render as one report:
64
+
65
+ ```sh
66
+ condition_coverage_collector 4570 /tmp/condition/runs # until stopped
67
+ ```
68
+
69
+ ```ruby
70
+ runs = Dir['/tmp/condition/runs/*.json']
71
+ ConditionCoverage::Render.from_json(config, runs)
72
+ ```
73
+
46
74
  ## What a run reports
47
75
 
48
76
  As the process exits:
49
77
 
50
78
  - `condition_coverage.json` is always written to `coverage_dir`: every site
51
79
  of every covered file, with each operand's true and false counts. It is
52
- the base the other reporters read.
80
+ the base the other reporters read, so they can also be run later, in
81
+ another process, from it alone:
82
+ `ConditionCoverage::Render.from_json(config)`, where `config` is a
83
+ `ConditionCoverage::Configuration` built from the same settings block.
84
+ Given several runs' reports, `Render.from_json(config, paths)` adds
85
+ their counts together first, so the report shows only the gaps that
86
+ every run left.
53
87
  - `:gaps` prints one line per missing outcome, in manifest order:
54
88
 
55
89
  ```text
@@ -79,7 +113,9 @@ An operand whose form allows one outcome only needs only to be evaluated:
79
113
  its other outcome is impossible, not a gap. `Foo.new`, literals (`'s'`,
80
114
  `1`, `:k`, `[]`, `{}`, `true`), lambdas and constants (`LATEST`,
81
115
  `Config::DEFAULT`, since one holding nil or false is rare) can only be
82
- truthy; `nil` and `false` only falsy:
116
+ truthy, as can `$stdout` and `$stderr`, which Ruby refuses to set to nil
117
+ or false. Other globals, `$stdin` among them, can be either. `nil` and
118
+ `false` can only be falsy:
83
119
 
84
120
  ```ruby
85
121
  @clock = options[:clock] || MonotonicClock.new # operand 1: never evaluated, or no gap
@@ -97,6 +133,12 @@ truthy; `nil` and `false` only falsy:
97
133
  the top of the page lists every site as a link to its line, covered
98
134
  sites first, then those with gaps, and arrows beside each site with
99
135
  gaps jump to the previous and next one, wrapping round at either end.
136
+ - `:post` sends the JSON report, as an HTTP POST, to the url set by
137
+ `collector 'http://...'`. It is for a run whose own files are thrown
138
+ away, such as a failing `docker build` step. It prints nothing, never
139
+ goes through an `http_proxy`, and gives up in silence on a collector
140
+ that cannot be reached or does not answer within a second, so the
141
+ run's output and exit status are unchanged.
100
142
 
101
143
  ## How it works
102
144
 
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+ require 'socket'
4
+ require 'condition_coverage/collector'
5
+
6
+ if ARGV.first == '-h'
7
+ puts <<~HELP
8
+ Usage: condition_coverage_collector PORT DIR
9
+
10
+ Listens on PORT and saves each JSON report the :post reporter
11
+ sends as DIR/run-0001.json, DIR/run-0002.json, and so on,
12
+ creating DIR if need be. Runs until stopped.
13
+
14
+ Options:
15
+ -h Show this help
16
+
17
+ Example:
18
+ condition_coverage_collector 4570 /tmp/condition/runs
19
+ HELP
20
+ exit 0
21
+ end
22
+
23
+ port, dir = ARGV
24
+ FileUtils.mkdir_p(dir)
25
+ server = TCPServer.new('0.0.0.0', Integer(port))
26
+ ConditionCoverage::Collector.serve(server, dir)
@@ -0,0 +1,5 @@
1
+ require_relative 'kernel'
2
+
3
+ # Required by RUBYOPT=-rcondition_coverage/auto, to cover a run
4
+ # with no change to its code, its settings all in ENV.
5
+ ConditionCoverage.start_from_env(ENV)
@@ -0,0 +1,46 @@
1
+ module ConditionCoverage
2
+ # Receives the JSON reports the :post reporter sends, so the
3
+ # runs that sent them, eg failing docker build steps, can
4
+ # be merged and rendered once they are all done.
5
+ module Collector
6
+ # Answers every request on server, forever, saving each
7
+ # body in dir as run-0001.json, run-0002.json, and so on,
8
+ # in the order they arrive. A connection sending no
9
+ # request, eg a check that the collector is up, is skipped.
10
+ def self.serve(server, dir)
11
+ n = 0
12
+ loop do
13
+ body = request_body(server.accept)
14
+ next if body.nil?
15
+
16
+ n += 1
17
+ File.write(format('%<dir>s/run-%<n>04d.json', dir:, n:), body)
18
+ end
19
+ end
20
+
21
+ # The body of the one HTTP request on client, after
22
+ # answering it 200; nil, unanswered, when client closes
23
+ # without sending a request.
24
+ def self.request_body(client)
25
+ headers = []
26
+ while (line = client.gets) && line != "\r\n"
27
+ headers << line
28
+ end
29
+ body = headers.empty? ? nil : answer(client, headers)
30
+ client.close
31
+ body
32
+ end
33
+
34
+ # The body of a request with headers, read from client,
35
+ # which is then answered 200.
36
+ def self.answer(client, headers)
37
+ length = headers.sum do |line|
38
+ name, value = line.split(':', 2)
39
+ name.casecmp?('content-length') ? value.to_i : 0
40
+ end
41
+ body = client.read(length)
42
+ client.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
43
+ body
44
+ end
45
+ end
46
+ end
@@ -5,8 +5,8 @@ module ConditionCoverage
5
5
  # The settings given in a ConditionCoverage.start block.
6
6
  class Configuration
7
7
  GLOB_FLAGS = File::FNM_PATHNAME | File::FNM_EXTGLOB
8
- SETTINGS = %w[cover coverage_dir report root].freeze
9
- REPORTERS = %w[gaps html].freeze
8
+ SETTINGS = %w[collector cover coverage_dir report root].freeze
9
+ REPORTERS = %w[gaps html post].freeze
10
10
 
11
11
  # Evaluates the settings block against a new configuration.
12
12
  def initialize(&)
@@ -23,6 +23,14 @@ module ConditionCoverage
23
23
  @coverage_dir
24
24
  end
25
25
 
26
+ # The url the :post reporter sends the JSON report to.
27
+ attr_reader :collector_url
28
+
29
+ # Sets the url the :post reporter sends the JSON report to.
30
+ def collector(url)
31
+ @collector_url = url
32
+ end
33
+
26
34
  # Sets the dir that cover globs and site names are
27
35
  # relative to.
28
36
  def root(dir)
@@ -1,13 +1,13 @@
1
1
  module ConditionCoverage
2
2
  # The terminal worklist: one line per missing outcome, and
3
3
  # nothing for a site seen both ways. Every count stays in
4
- # the Result for reporters that show them all.
4
+ # the operands for reporters that show them all.
5
5
  module Gaps
6
- # The gap lines of result, eg "k.rb:2:2 operand 0 never
7
- # false", in manifest order.
8
- def self.lines(result)
9
- result.operands.flat_map do |site, operands|
10
- site_gaps(site, operands)
6
+ # The gap lines of operands, as Result#operands gives them,
7
+ # eg "k.rb:2:2 operand 0 never false", in manifest order.
8
+ def self.lines(operands)
9
+ operands.flat_map do |site, counts|
10
+ site_gaps(site, counts)
11
11
  end
12
12
  end
13
13
 
@@ -10,23 +10,25 @@ module ConditionCoverage
10
10
  # each covered file, written to the configuration's
11
11
  # output_dir.
12
12
  module HtmlReport
13
- # Writes every page. files is { full path => {source:, sites:} }
14
- # for each covered file, as the kernel parsed it.
15
- def self.write(result, files, config)
13
+ # Writes every page. operands are as Result#operands gives
14
+ # them; files is { full path => {source:, sites:} } for each
15
+ # covered file, as the kernel parsed it.
16
+ def self.write(operands, files, config)
16
17
  dir = config.output_dir
17
- index = HtmlIndex.page(Summary.files(result))
18
+ index = HtmlIndex.page(Summary.files(operands))
18
19
  File.write("#{dir}/index.html", index)
19
20
  files.each do |full_path, parsed|
20
21
  path = config.relative(full_path)
21
- write_page("#{dir}/files/#{path}.html", path, parsed, result)
22
+ out = "#{dir}/files/#{path}.html"
23
+ write_page(out, path, parsed, operands)
22
24
  end
23
25
  end
24
26
 
25
27
  # Writes one file's page to out.
26
- def self.write_page(out, path, parsed, result)
28
+ def self.write_page(out, path, parsed, operands)
27
29
  source = parsed[:source]
28
- lines = SourceLines.of(source, parsed[:sites], result.operands)
29
- trees = trees(source, parsed[:sites], lines, result.operands)
30
+ lines = SourceLines.of(source, parsed[:sites], operands)
31
+ trees = trees(source, parsed[:sites], lines, operands)
30
32
  FileUtils.mkdir_p(File.dirname(out))
31
33
  File.write(out, HtmlFile.page(path, lines, trees))
32
34
  end
@@ -4,10 +4,15 @@ module ConditionCoverage
4
4
  # The base reporter: the result as raw JSON, which every
5
5
  # other reporter can read back.
6
6
  module JsonWriter
7
- # Writes result to path as {"sites": {site: [counts, ...]}}.
8
- def self.write(result, path)
9
- json = JSON.pretty_generate('sites' => result.operands)
10
- File.write(path, json)
7
+ # Writes operands, as Result#operands gives them, to path as
8
+ # {"sites": {site: [counts, ...]}}.
9
+ def self.write(operands, path)
10
+ File.write(path, json(operands))
11
+ end
12
+
13
+ # operands as the JSON report's text.
14
+ def self.json(operands)
15
+ JSON.pretty_generate('sites' => operands)
11
16
  end
12
17
  end
13
18
  end
@@ -1,10 +1,9 @@
1
1
  require 'fileutils'
2
2
  require_relative 'configuration'
3
- require_relative 'gaps'
4
- require_relative 'html_report'
5
3
  require_relative 'json_writer'
6
4
  require_relative 'manifest'
7
5
  require_relative 'recorder'
6
+ require_relative 'render'
8
7
  require_relative 'result'
9
8
  require_relative 'rewriter'
10
9
 
@@ -47,6 +46,27 @@ module ConditionCoverage
47
46
  on_exit.call { report(config) }
48
47
  end
49
48
 
49
+ # Starts from settings in env alone, for a run whose code
50
+ # cannot be changed, eg one started by RUBYOPT=-r: covers
51
+ # the comma-separated CONDITION_COVERAGE_COVER globs under
52
+ # CONDITION_COVERAGE_ROOT, writing the JSON report into
53
+ # CONDITION_COVERAGE_DIR and POSTing it to
54
+ # CONDITION_COVERAGE_COLLECTOR. Without a collector it
55
+ # starts nothing, so RUBYOPT can be set for every run.
56
+ def self.start_from_env(env, on_exit: method(:at_exit))
57
+ return unless env.key?('CONDITION_COVERAGE_COLLECTOR')
58
+
59
+ start(on_exit:) do
60
+ root env.fetch('CONDITION_COVERAGE_ROOT')
61
+ env.fetch('CONDITION_COVERAGE_COVER').split(',').each do |glob|
62
+ cover glob
63
+ end
64
+ coverage_dir env.fetch('CONDITION_COVERAGE_DIR')
65
+ collector env.fetch('CONDITION_COVERAGE_COLLECTOR')
66
+ report :post
67
+ end
68
+ end
69
+
50
70
  # Routes every later load through instrumented, under
51
71
  # whichever configuration is current. Installed once: a
52
72
  # second definition would warn that it was redefined.
@@ -62,37 +82,28 @@ module ConditionCoverage
62
82
  def self.instrumented(path, config)
63
83
  return unless config.covers?(path)
64
84
 
65
- parsed = @parsed[path] = parse(path, config)
85
+ parsed = @parsed[path] = Manifest.parse(path, config)
66
86
  rewritten = Rewriter.rewrite(parsed[:source], parsed[:sites])
67
87
  RubyVM::InstructionSequence.compile(rewritten, path, path)
68
88
  end
69
89
 
70
- # The source and sites of the covered file at path.
71
- def self.parse(path, config)
72
- source = File.read(path)
73
- { source:, sites: Manifest.sites(source, config.relative(path)) }
74
- end
75
-
76
90
  # Writes the JSON report, then runs the chosen reporters, for
77
91
  # every covered file whether or not the run loaded it, into
78
92
  # coverage_dir, created first if need be.
79
93
  def self.report(config)
80
94
  files = parsed_files(config)
81
- result = Result.new(merged_sites(files), @recorder.counts)
82
- FileUtils.mkdir_p(config.output_dir)
83
- JsonWriter.write(result, config.json_path)
84
- run_reporters(result, files, config)
95
+ operands = collect(files, config)
96
+ Render.run(operands, files, config)
85
97
  end
86
98
 
87
- # Runs each reporter chosen in the start block.
88
- def self.run_reporters(result, files, config)
89
- reporters = config.reporters
90
- if reporters.include?(:gaps)
91
- Gaps.lines(result).each { |line| puts line }
92
- end
93
- return unless reporters.include?(:html)
94
-
95
- HtmlReport.write(result, files, config)
99
+ # The operands of every covered file, with the counts this
100
+ # run recorded, once written as the JSON report.
101
+ def self.collect(files, config)
102
+ sites = merged_sites(files)
103
+ operands = Result.new(sites, @recorder.counts).operands
104
+ FileUtils.mkdir_p(config.output_dir)
105
+ JsonWriter.write(operands, config.json_path)
106
+ operands
96
107
  end
97
108
 
98
109
  # { full path => {source:, sites:} } for every covered file:
@@ -100,7 +111,7 @@ module ConditionCoverage
100
111
  # never loaded.
101
112
  def self.parsed_files(config)
102
113
  config.files.to_h do |path|
103
- [path, @parsed.fetch(path) { parse(path, config) }]
114
+ [path, @parsed.fetch(path) { Manifest.parse(path, config) }]
104
115
  end
105
116
  end
106
117
 
@@ -13,6 +13,12 @@ module ConditionCoverage
13
13
  collector.sites
14
14
  end
15
15
 
16
+ # The source and sites of the covered file at full_path.
17
+ def self.parse(full_path, config)
18
+ source = File.read(full_path)
19
+ { source:, sites: sites(source, config.relative(full_path)) }
20
+ end
21
+
16
22
  # Collects each site and its Condition.
17
23
  class ConditionSites < Prism::Visitor
18
24
  # Starts with no sites found.
@@ -97,13 +103,22 @@ module ConditionCoverage
97
103
  ].freeze
98
104
  FALSY_TYPES = %i[nil_node false_node].freeze
99
105
 
106
+ # Ruby raises TypeError on assigning nil or false to these.
107
+ TRUTHY_GLOBALS = %i[$stdout $stderr].freeze
108
+
100
109
  # The one outcome a leaf can have, eg :truthy for `Clock.new`
101
110
  # or a literal, :falsy for `nil`; nil for a leaf that can
102
111
  # have either.
103
112
  def fixed_outcome(node)
104
113
  return :falsy if FALSY_TYPES.include?(node.type)
105
114
 
106
- :truthy if TRUTHY_TYPES.include?(node.type) || new_call?(node)
115
+ :truthy if truthy_only?(node)
116
+ end
117
+
118
+ # True for a leaf that can never be falsy.
119
+ def truthy_only?(node)
120
+ TRUTHY_TYPES.include?(node.type) || new_call?(node) ||
121
+ truthy_global?(node)
107
122
  end
108
123
 
109
124
  # True for a call to new, eg `Clock.new`.
@@ -111,6 +126,12 @@ module ConditionCoverage
111
126
  node.type == :call_node && node.name == :new
112
127
  end
113
128
 
129
+ # True for $stdout or $stderr, which can never be falsy.
130
+ def truthy_global?(node)
131
+ node.type == :global_variable_read_node &&
132
+ TRUTHY_GLOBALS.include?(node.name)
133
+ end
134
+
114
135
  # True for a call to a named method with arguments and no
115
136
  # parentheses, eg `id.include? 'N'`, but not `id.size == 6`.
116
137
  def command?(node)
@@ -0,0 +1,22 @@
1
+ module ConditionCoverage
2
+ # Combines the operands of several runs of the same source.
3
+ module Merge
4
+ # One operands hash, as Result#operands gives them, whose
5
+ # counts are the sums of every run's.
6
+ def self.operands(runs)
7
+ runs.reduce do |total, run|
8
+ total.merge(run) do |_site, mine, theirs|
9
+ mine.zip(theirs).map { |a, b| sum(a, b) }
10
+ end
11
+ end
12
+ end
13
+
14
+ # One operand's counts from two runs, added; a fixed leaf's
15
+ # only comes from the source, so either run's will do.
16
+ def self.sum(mine, theirs)
17
+ mine.merge(theirs) do |key, a, b|
18
+ key == :only ? a : a + b
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require 'net/http'
2
+ require_relative 'json_writer'
3
+
4
+ module ConditionCoverage
5
+ # The :post reporter: sends the JSON report to a collector,
6
+ # for runs whose own files are thrown away, eg a failing
7
+ # docker build step.
8
+ module Post
9
+ # POSTs operands, as the JSON report, to url, never through
10
+ # an http_proxy, which could not reach a collector local to
11
+ # the run. A collector that cannot be reached, or does not
12
+ # answer within a second, is passed over in silence: the
13
+ # run's own output and exit status must stay as they are.
14
+ def self.deliver(operands, url)
15
+ uri = URI(url)
16
+ http = Net::HTTP.new(uri.host, uri.port, nil)
17
+ http.open_timeout = http.read_timeout = 1
18
+ headers = { 'Content-Type' => 'application/json' }
19
+ http.post(uri.path, JsonWriter.json(operands), headers)
20
+ rescue StandardError
21
+ nil
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+ require_relative 'gaps'
3
+ require_relative 'html_report'
4
+ require_relative 'manifest'
5
+ require_relative 'merge'
6
+ require_relative 'post'
7
+
8
+ module ConditionCoverage
9
+ # The reporters, run from operands alone: needing nothing the
10
+ # run recorded beyond them, they can run in another process.
11
+ module Render
12
+ # Runs each reporter chosen in config. operands are as
13
+ # Result#operands gives them; files is { full path =>
14
+ # {source:, sites:} } for each covered file.
15
+ def self.run(operands, files, config)
16
+ reporters = config.reporters
17
+ if reporters.include?(:gaps)
18
+ Gaps.lines(operands).each { |line| puts line }
19
+ end
20
+ if reporters.include?(:html)
21
+ HtmlReport.write(operands, files, config)
22
+ end
23
+ return unless reporters.include?(:post)
24
+
25
+ Post.deliver(operands, config.collector_url)
26
+ end
27
+
28
+ # Runs the reporters chosen in config from the JSON reports
29
+ # at paths, their counts merged, parsing each covered file
30
+ # afresh. paths is config.json_path alone by default.
31
+ def self.from_json(config, paths = [config.json_path])
32
+ operands = Merge.operands(paths.map { |path| read(path) })
33
+ files = config.files.to_h do |path|
34
+ [path, Manifest.parse(path, config)]
35
+ end
36
+ run(operands, files, config)
37
+ end
38
+
39
+ # The operands of the JSON report at path.
40
+ def self.read(path)
41
+ json = JSON.parse(File.read(path))
42
+ json['sites'].transform_values do |leaves|
43
+ leaves.map { |leaf| counts(leaf) }
44
+ end
45
+ end
46
+
47
+ # One operand's counts as JSON holds them, with the keys,
48
+ # and any only: value, as the symbols Result#operands uses.
49
+ def self.counts(leaf)
50
+ counts = { truthy: leaf['truthy'], falsy: leaf['falsy'] }
51
+ only = leaf['only']
52
+ only ? counts.merge(only: only.to_sym) : counts
53
+ end
54
+ end
55
+ end
@@ -1,9 +1,10 @@
1
1
  module ConditionCoverage
2
2
  # The per-file numbers the HTML index page shows.
3
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) }
4
+ # One row per file that has sites in operands, as
5
+ # Result#operands gives them, in path order.
6
+ def self.files(operands)
7
+ by_path = operands.group_by { |site, _| path_of(site) }
7
8
  by_path.sort.map { |path, sites| row(path, sites) }
8
9
  end
9
10
 
@@ -1,3 +1,3 @@
1
1
  module ConditionCoverage
2
- VERSION = '0.0.5'.freeze
2
+ VERSION = '0.0.6'.freeze
3
3
  end
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.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Jagger
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -25,13 +25,17 @@ dependencies:
25
25
  version: '1.0'
26
26
  description: Records whether each operand of every &&, ||, and and or condition was
27
27
  seen both true and false, and reports the gaps in the terminal and as HTML.
28
- executables: []
28
+ executables:
29
+ - condition_coverage_collector
29
30
  extensions: []
30
31
  extra_rdoc_files: []
31
32
  files:
32
33
  - LICENSE.md
33
34
  - README.md
35
+ - exe/condition_coverage_collector
34
36
  - lib/condition_coverage.rb
37
+ - lib/condition_coverage/auto.rb
38
+ - lib/condition_coverage/collector.rb
35
39
  - lib/condition_coverage/condition.rb
36
40
  - lib/condition_coverage/configuration.rb
37
41
  - lib/condition_coverage/gaps.rb
@@ -49,7 +53,10 @@ files:
49
53
  - lib/condition_coverage/json_writer.rb
50
54
  - lib/condition_coverage/kernel.rb
51
55
  - lib/condition_coverage/manifest.rb
56
+ - lib/condition_coverage/merge.rb
57
+ - lib/condition_coverage/post.rb
52
58
  - lib/condition_coverage/recorder.rb
59
+ - lib/condition_coverage/render.rb
53
60
  - lib/condition_coverage/result.rb
54
61
  - lib/condition_coverage/rewriter.rb
55
62
  - lib/condition_coverage/site.rb