padded_cell 0.1.0 → 0.3.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/Cargo.lock +16 -2
  4. data/Cargo.toml +2 -0
  5. data/README.md +170 -8
  6. data/ext/padded_cell_native/core/Cargo.toml +2 -1
  7. data/ext/padded_cell_native/core/src/chart/directives/tests.rs +56 -0
  8. data/ext/padded_cell_native/core/src/chart/directives.rs +144 -0
  9. data/ext/padded_cell_native/core/src/chart/json/tests.rs +46 -0
  10. data/ext/padded_cell_native/core/src/chart/json.rs +175 -0
  11. data/ext/padded_cell_native/core/src/chart/lcov.rs +50 -0
  12. data/ext/padded_cell_native/core/src/chart/markdown/tests.rs +93 -0
  13. data/ext/padded_cell_native/core/src/chart/markdown.rs +201 -0
  14. data/ext/padded_cell_native/core/src/chart/methods/tests.rs +38 -0
  15. data/ext/padded_cell_native/core/src/chart/methods.rs +62 -0
  16. data/ext/padded_cell_native/core/src/chart/output/tests.rs +45 -0
  17. data/ext/padded_cell_native/core/src/chart/output.rs +103 -0
  18. data/ext/padded_cell_native/core/src/chart/results/tests.rs +96 -0
  19. data/ext/padded_cell_native/core/src/chart/results.rs +379 -0
  20. data/ext/padded_cell_native/core/src/chart/source/tests.rs +64 -0
  21. data/ext/padded_cell_native/core/src/chart/source.rs +153 -0
  22. data/ext/padded_cell_native/core/src/chart/stats/tests.rs +52 -0
  23. data/ext/padded_cell_native/core/src/chart/stats.rs +218 -0
  24. data/ext/padded_cell_native/core/src/chart/test_support.rs +93 -0
  25. data/ext/padded_cell_native/core/src/chart/tests.rs +97 -0
  26. data/ext/padded_cell_native/core/src/chart/thresholds/tests.rs +36 -0
  27. data/ext/padded_cell_native/core/src/chart/thresholds.rs +116 -0
  28. data/ext/padded_cell_native/core/src/chart.rs +302 -0
  29. data/ext/padded_cell_native/core/src/discover/tests.rs +2 -0
  30. data/ext/padded_cell_native/core/src/discover.rs +7 -53
  31. data/ext/padded_cell_native/core/src/error.rs +24 -0
  32. data/ext/padded_cell_native/core/src/lib.rs +10 -3
  33. data/ext/padded_cell_native/core/src/parallel.rs +43 -0
  34. data/ext/padded_cell_native/core/src/restarts/tests.rs +29 -0
  35. data/ext/padded_cell_native/core/src/restarts.rs +64 -0
  36. data/ext/padded_cell_native/ffi/Cargo.toml +2 -1
  37. data/ext/padded_cell_native/ffi/src/chart.rs +205 -0
  38. data/ext/padded_cell_native/ffi/src/lib.rs +5 -0
  39. data/ext/padded_cell_native/ffi/src/limits.rs +83 -0
  40. data/lib/padded_cell/asylum.rb +93 -0
  41. data/lib/padded_cell/cell.rb +23 -14
  42. data/lib/padded_cell/chart/accumulator.rb +103 -0
  43. data/lib/padded_cell/chart/config.rb +129 -0
  44. data/lib/padded_cell/chart/delta.rb +148 -0
  45. data/lib/padded_cell/chart/document.rb +64 -0
  46. data/lib/padded_cell/chart/filter.rb +55 -0
  47. data/lib/padded_cell/chart/merge.rb +63 -0
  48. data/lib/padded_cell/chart/probe.rb +70 -0
  49. data/lib/padded_cell/chart/raw.rb +48 -0
  50. data/lib/padded_cell/chart/report.rb +128 -0
  51. data/lib/padded_cell/chart/source_files.rb +90 -0
  52. data/lib/padded_cell/chart.rb +38 -0
  53. data/lib/padded_cell/cli.rb +70 -4
  54. data/lib/padded_cell/limits.rb +45 -0
  55. data/lib/padded_cell/loader.rb +5 -1
  56. data/lib/padded_cell/pool.rb +82 -58
  57. data/lib/padded_cell/reporters.rb +20 -3
  58. data/lib/padded_cell/results.rb +13 -0
  59. data/lib/padded_cell/runner.rb +150 -22
  60. data/lib/padded_cell/version.rb +1 -1
  61. data/lib/padded_cell/worker.rb +63 -0
  62. data/lib/padded_cell.rb +8 -0
  63. metadata +43 -1
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+ require 'time'
6
+
7
+ module PaddedCell
8
+ module Chart
9
+ # Writes the reports (natively), prints the summary, and reports the
10
+ # thresholds missed.
11
+ class Report
12
+ Stats = Data.define(:covered, :missed, :omitted, :hits, :percent, :strength) do
13
+ def total = covered + missed
14
+ end
15
+ CRITERIA = { lines: :line, branches: :branch, methods: :method }.freeze
16
+ WORST_FILES = 5
17
+
18
+ def initialize(document, dir:, io:)
19
+ @document = document
20
+ @settings = document.settings
21
+ @dir = dir
22
+ @io = io
23
+ end
24
+
25
+ # check: a whole, passing run: thresholds decide, and .last_run.json
26
+ # records it. False when a threshold is missed.
27
+ def call(check:)
28
+ result = PaddedCellNative.chart_report(@document.rows, @document.group_rows, options(check))
29
+ @total = stats(result[:total])
30
+ @files = result[:files].map { |name, file| [name, stats(file)] }
31
+ summary
32
+ warn_lost
33
+ return true unless check
34
+
35
+ result[:violations].each { report_violation(*it) }
36
+ result[:violations].empty?
37
+ end
38
+
39
+ private
40
+
41
+ def options(check)
42
+ now = Time.now
43
+ root = @document.root
44
+ {
45
+ dir: File.expand_path(@dir), formats: @settings[:formats], nocov_token: @settings[:nocov_token],
46
+ criteria: criteria.map(&:to_s), write_last_run: check, last_run: last_run,
47
+ minimum: limits(@settings[:minimum]), minimum_by_file: limits(@settings[:minimum_by_file]),
48
+ minimum_by_group: @settings[:minimum_by_group].map { |name, minimums| [name.to_s, limits(minimums)] },
49
+ maximum_drop: limits(@settings[:maximum_drop]),
50
+ root:, timestamp: now.iso8601(3), created_at: now.to_f, commit: commit(root),
51
+ project_name: File.basename(root).capitalize.tr('_', ' '), generator: "padded_cell #{VERSION}"
52
+ }
53
+ end
54
+
55
+ # [line, branch, method], nil where unset.
56
+ def limits(values) = %i[line branch method].map { values[it]&.to_f }
57
+
58
+ # The previous whole run's percents.
59
+ def last_run
60
+ previous = JSON.parse(File.read(File.join(@dir, '.last_run.json')), symbolize_names: true)[:result] || {}
61
+ limits({ line: previous[:line] || previous[:covered_percent], branch: previous[:branch],
62
+ method: previous[:method] })
63
+ rescue Errno::ENOENT, JSON::ParserError
64
+ [nil, nil, nil]
65
+ end
66
+
67
+ def commit(root)
68
+ out, status = Open3.capture2e('git', '-C', root, 'rev-parse', 'HEAD')
69
+ out.strip if status.success?
70
+ rescue SystemCallError
71
+ nil
72
+ end
73
+
74
+ def stats(row) = row.transform_values { Stats.new(*it) }
75
+
76
+ # The criteria measured, as stats keys.
77
+ def criteria = @document.criteria.filter_map { CRITERIA[it.to_sym] }
78
+
79
+ def summary
80
+ @io.puts "\nCoverage report generated for padded_cell to #{@dir}"
81
+ criteria.each do |criterion|
82
+ stat = @total[criterion]
83
+ next if criterion != :line && stat.total.zero?
84
+
85
+ @io.puts format('%<label>s coverage: %<covered>d / %<total>d (%<percent>.2f%%)',
86
+ label: criterion.capitalize, covered: stat.covered, total: stat.total,
87
+ percent: stat.percent.floor(2))
88
+ end
89
+ end
90
+
91
+ def warn_lost
92
+ lost = @document.lost
93
+ unmeasured = @document.unmeasured
94
+ if lost[:workers].positive? || lost[:cells].positive?
95
+ @io.puts "Coverage is partial: #{lost[:workers]} workers and #{lost[:cells]} cells died before handing " \
96
+ 'over what they measured'
97
+ end
98
+ return if unmeasured.empty?
99
+
100
+ @io.puts "#{unmeasured.size} tracked files were loaded before coverage started, and are not reported: " \
101
+ "#{unmeasured.first(3).join(', ')}#{', ...' if unmeasured.size > 3}"
102
+ end
103
+
104
+ def report_violation(scope, name, criterion, limit, actual)
105
+ label = criterion.capitalize
106
+ @io.puts case scope
107
+ when :drop
108
+ format('%<label>s coverage has dropped by %<actual>.2f%% since the last time ' \
109
+ '(maximum allowed: %<limit>.2f%%).', label:, actual:, limit:)
110
+ when :total then format('%<label>s coverage (%<actual>.2f%%) is below the expected minimum ' \
111
+ 'coverage (%<limit>.2f%%).', label:, actual:, limit:)
112
+ else format('%<label>s coverage by %<scope>s (%<actual>.2f%%) is below the expected minimum ' \
113
+ 'coverage (%<limit>.2f%%) in %<name>s.', label:, scope:, actual:, limit:, name:)
114
+ end
115
+ worst_files(criterion) if scope == :total
116
+ end
117
+
118
+ def worst_files(criterion)
119
+ worst = @files.map { |name, file| [name, file[criterion].percent] }.select { it[1] < 100 }
120
+ .min_by(WORST_FILES) { it[1] }
121
+ return if worst.empty?
122
+
123
+ @io.puts " Lowest-coverage files (#{criterion}):"
124
+ worst.each { |name, percent| @io.puts format(' %<percent>6.2f%% %<name>s', percent:, name:) }
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaddedCell
4
+ module Chart
5
+ # The files of a report: those measured and those tracked but never
6
+ # loaded, minus the filtered ones, in path order; and their groups.
7
+ class SourceFiles
8
+ # Tracked templates are not Ruby files Coverage could stub.
9
+ TEMPLATES = /\.(?:erb|haml|slim)\z/
10
+
11
+ # Tracked files loaded before measuring started (by a Rails rake task,
12
+ # Bundler.require...): their coverage is unknown, not 0%.
13
+ attr_reader :unmeasured
14
+
15
+ # files: Raw's {relative path => {lines:, branches:, methods:}}
16
+ def initialize(files, config:, root:)
17
+ @config = config
18
+ @root = root
19
+ loaded = loaded_features
20
+ never, @unmeasured = tracked(files).partition { !loaded.include?(File.join(root, it)) }
21
+ @rows = (files.map { |name, entry| row(name, entry) } + stubs(never))
22
+ .reject { |path, name| skipped?(path, name) }
23
+ .sort_by { it[1] }
24
+ end
25
+
26
+ # [[absolute path, name, lines, branches, methods, loaded], ...], for
27
+ # PaddedCellNative.chart_report.
28
+ attr_reader :rows
29
+
30
+ # [[name, [row index, ...]], ...]: the groups with files, then
31
+ # Ungrouped. None without groups.
32
+ def groups
33
+ return [] if @config.groups.empty?
34
+
35
+ files = @rows.map { |path, name| SourceFile.new(path, name) }
36
+ grouped = @config.groups.map { |name, filter| [name, files.each_index.select { filter.call(files[it]) }] }
37
+ ungrouped = files.each_index.to_a - grouped.flat_map(&:last)
38
+ (grouped + [['Ungrouped', ungrouped]]).reject { |_, members| members.empty? }
39
+ end
40
+
41
+ private
42
+
43
+ def row(name, entry) = [File.join(@root, name), name, entry[:lines], entry[:branches], entry[:methods], true]
44
+
45
+ # Files never loaded. Ruby compiles them, without running them: while
46
+ # Coverage runs, that gives their lines and branches as if loaded, none
47
+ # covered. Their methods only exist once run: the native side finds
48
+ # them in the source. A file that does not compile has every line but
49
+ # blank and comment ones missed.
50
+ def stubs(names)
51
+ paths = names.map { File.join(@root, it) }
52
+ compiled = paths.select { compile(it) }
53
+ measured = compiled.empty? ? {} : ::Coverage.peek_result
54
+ paths.zip(names).map do |path, name|
55
+ data = measured[path]
56
+ next [path, name, File.readlines(path).map { 0 }, nil, nil, false] unless data
57
+
58
+ entry = Raw.file(Delta.dense(data))
59
+ [path, name, entry[:lines], entry[:branches], nil, false]
60
+ end
61
+ end
62
+
63
+ def compile(path)
64
+ RubyVM::InstructionSequence.compile_file(path)
65
+ rescue SyntaxError
66
+ false
67
+ end
68
+
69
+ def tracked(files)
70
+ @config.tracked.flat_map { Dir.glob(it, base: @root) }.uniq.reject do |name|
71
+ files.key?(name) || TEMPLATES.match?(name) || !File.file?(File.join(@root, name))
72
+ end
73
+ end
74
+
75
+ def skipped?(path, name)
76
+ file = SourceFile.new(path, name)
77
+ @config.filters.any? { it.call(file) }
78
+ end
79
+
80
+ # Required files, also under the root's real path.
81
+ def loaded_features
82
+ pwd = "#{Dir.pwd}/"
83
+ $LOADED_FEATURES.each_with_object(Set.new) do |feature, set|
84
+ set << feature
85
+ set << File.join(@root, feature.delete_prefix(pwd)) if feature.start_with?(pwd)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'chart/delta'
4
+ require_relative 'chart/probe'
5
+ require_relative 'chart/raw'
6
+ require_relative 'chart/accumulator'
7
+ require_relative 'chart/filter'
8
+ require_relative 'chart/config'
9
+ require_relative 'chart/source_files'
10
+ require_relative 'chart/document'
11
+ require_relative 'chart/merge'
12
+ require_relative 'chart/report'
13
+
14
+ module PaddedCell
15
+ # Built-in coverage: Ruby's Coverage measured in the
16
+ # runner, every worker and every cell, summed exactly in the runner. The
17
+ # chart, as the asylum keeps one per patient: inside, a name that is not
18
+ # Ruby's own Coverage; outside, it is all --coverage.
19
+ #
20
+ # Measuring starts before the suite loads, so what is measured comes from
21
+ # the command line (--coverage=lines,branches,methods,eval); the report is
22
+ # configured in the test helper, with PaddedCell.coverage (see Config).
23
+ module Chart
24
+ CRITERIA = %i[lines branches methods eval].freeze
25
+ DEFAULT_CRITERIA = %i[lines branches].freeze
26
+
27
+ class << self
28
+ # Every PaddedCell.coverage block, in one Config.
29
+ def config = @config ||= Config.new
30
+
31
+ def configure(profile = nil, &block)
32
+ config.profile(profile) if profile
33
+ config.instance_eval(&block) if block
34
+ config
35
+ end
36
+ end
37
+ end
38
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'optparse'
4
4
  require_relative 'runner'
5
+ require_relative 'chart'
5
6
 
6
7
  module PaddedCell
7
8
  # The padded_cell command.
@@ -18,22 +19,33 @@ module PaddedCell
18
19
 
19
20
  module_function
20
21
 
21
- # Exit status: 0 passed, 1 failed, 2 usage or load error, 130 interrupted.
22
+ # Exit status: 0 passed, 1 failed, 2 usage or load error, 3 coverage below
23
+ # a minimum, 130 interrupted.
22
24
  def start(argv, io: $stdout)
23
- options = { io: }
25
+ options = { io:, coverage: coverage_criteria(ENV.fetch('PADDED_CELL_COVERAGE', nil)), **report_paths }
24
26
  parser(options).parse!(argv)
27
+ return merge(options) if options[:coverage_merge]
28
+
25
29
  options[:paths] = argv
26
30
  Runner.new(**options).run
27
- rescue OptionParser::ParseError, ArgumentError, LoadFailure, Unsupported, SystemCallError => e
31
+ rescue OptionParser::ParseError, ArgumentError, RegexpError, LoadFailure, Unsupported, SystemCallError => e
28
32
  warn "padded_cell: #{e.message}"
29
33
  2
30
34
  end
31
35
 
36
+ # Report paths a CI runner can set without touching the command line;
37
+ # --json and --junit still win.
38
+ def report_paths
39
+ { json: ENV.fetch('PADDED_CELL_JSON', nil), junit: ENV.fetch('PADDED_CELL_JUNIT', nil) }
40
+ .reject { |_, path| path.nil? || path.empty? }
41
+ end
42
+
32
43
  def parser(options)
33
44
  OptionParser.new do |opts|
34
45
  opts.banner = BANNER
35
46
  opts.version = VERSION
36
47
  runner_options(opts, options)
48
+ coverage_options(opts, options)
37
49
  output_options(opts, options)
38
50
  opts.on('--version', 'Print the version') do
39
51
  options[:io].puts "padded_cell #{VERSION}"
@@ -50,7 +62,7 @@ module PaddedCell
50
62
  opts.on('-j', '--jobs N', Integer, "Workers (default: #{Etc.nprocessors})") do
51
63
  options[:jobs] = positive(it)
52
64
  end
53
- opts.on('-s', '--seed SEED', Integer, 'Order seed (default: random, or $SEED)') { options[:seed] = it }
65
+ opts.on('-s', '--seed SEED', Integer, 'Order seed (default: random, or $SEED)') { options[:seed] = seed(it) }
54
66
  opts.on('-n', '-i', '--name', '--include PATTERN', 'Only tests matching (/regexp/ or exact name)') do
55
67
  options[:include] = it
56
68
  end
@@ -65,10 +77,41 @@ module PaddedCell
65
77
  opts.on('--timeout SECONDS', Float, 'Kill and fail a test running longer') do
66
78
  options[:timeout] = positive(it)
67
79
  end
80
+ opts.on('--budget SECONDS', Float, 'Stop dispatching once the run has taken SECONDS (and kill and ' \
81
+ 'fail what runs past it); tests left out fail the run') do
82
+ options[:budget] = positive(it)
83
+ end
68
84
  opts.on('--fail-fast', 'Stop dispatching after the first failure') { options[:fail_fast] = true }
85
+ opts.on('--asylum[=N]', Integer, 'Retry failed tests up to N times (default 2) after the run, alone, ' \
86
+ 'each in a fresh cell; report the flaky ones') do
87
+ options[:asylum] = it.nil? ? 2 : positive(it)
88
+ end
69
89
  opts.on('--list', 'List the tests that would run (static, loads nothing)') { options[:list] = true }
70
90
  end
71
91
 
92
+ def coverage_options(opts, options)
93
+ opts.on('--[no-]coverage[=CRITERIA]', 'Measure coverage: lines, branches, methods, eval ' \
94
+ "(default: #{Chart::DEFAULT_CRITERIA.join(',')}; " \
95
+ 'or $PADDED_CELL_COVERAGE)') do
96
+ options[:coverage] = coverage_criteria(it.to_s)
97
+ end
98
+ opts.on('--coverage-dir DIR', 'Write coverage reports to DIR (default: coverage, or the test helper\'s)') do
99
+ options[:coverage_dir] = it
100
+ end
101
+ opts.on('--coverage-merge DIR,DIR', Array, 'Merge the coverage of shards (their --coverage-dir) ' \
102
+ 'into one report; runs no test') do
103
+ options[:coverage_merge] = it
104
+ end
105
+ end
106
+
107
+ # --coverage-merge: 0, or 3 when a threshold is missed.
108
+ def merge(options)
109
+ document = Chart::Merge.call(options[:coverage_merge])
110
+ dir = options[:coverage_dir] || 'coverage'
111
+ document.write(dir)
112
+ Chart::Report.new(document, dir:, io: options[:io]).call(check: document.complete?) ? 0 : 3
113
+ end
114
+
72
115
  def output_options(opts, options)
73
116
  opts.on('-v', '--verbose', 'One line per test, with timings') { options[:verbose] = true }
74
117
  opts.on('--show-skips', 'Show skipped tests at the end') { options[:show_skips] = true }
@@ -78,6 +121,13 @@ module PaddedCell
78
121
  opts.on('--no-timings', 'Neither read nor write timings') { options[:timings] = nil }
79
122
  end
80
123
 
124
+ # The planner takes an unsigned 64-bit seed.
125
+ def seed(value)
126
+ raise OptionParser::InvalidArgument, "#{value} (must be 0 to 2**64 - 1)" unless value.between?(0, (2**64) - 1)
127
+
128
+ value
129
+ end
130
+
81
131
  # OptionParser names the switch in the message.
82
132
  def positive(value)
83
133
  raise OptionParser::InvalidArgument, "#{value} (must be positive)" unless value.positive?
@@ -85,6 +135,22 @@ module PaddedCell
85
135
  value
86
136
  end
87
137
 
138
+ # "branches,methods" => [:lines, :branches, :methods] (lines always);
139
+ # "1" or "true" => the defaults; nil, "", "0" or "false" => off.
140
+ def coverage_criteria(value)
141
+ value = value.to_s.strip.downcase
142
+ return if ['', '0', 'false'].include?(value)
143
+ return Chart::DEFAULT_CRITERIA if %w[1 true].include?(value)
144
+
145
+ names = value.split(',').map { it.strip.to_sym }
146
+ unknown = names - Chart::CRITERIA
147
+ unless unknown.empty?
148
+ raise OptionParser::InvalidArgument, "coverage #{unknown.join(',')} (known: #{Chart::CRITERIA.join(', ')})"
149
+ end
150
+
151
+ [:lines] | names
152
+ end
153
+
88
154
  def shard(value)
89
155
  index, count = value.split('/', 2).map { Integer(it, 10, exception: false) }
90
156
  unless index && count&.positive? && index.between?(1, count)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaddedCell
4
+ # What cuts a test short: its --timeout, and what is left of the run's
5
+ # --budget. A test's deadline never outlives the run's. Deadlines are
6
+ # PaddedCellNative::Deadline values in monotonic nanoseconds: one clock for
7
+ # the runner, its workers and their cells.
8
+ class Limits
9
+ NS = 1_000_000_000
10
+
11
+ def self.now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
12
+
13
+ # Seconds left before deadline (0.0 once passed), nil when never.
14
+ def self.remaining(deadline, now = self.now) = deadline.remaining_ns(now)&./(NS.to_f)
15
+
16
+ attr_reader :timeout, :budget
17
+
18
+ # timeout: seconds per test, or nil
19
+ # budget: seconds for the whole run from started, or nil
20
+ def initialize(timeout: nil, budget: nil, started: Limits.now)
21
+ @timeout = timeout
22
+ @budget = budget
23
+ @run = budget ? PaddedCellNative::Deadline.after(started, nanoseconds(budget)) : PaddedCellNative::Deadline.never
24
+ end
25
+
26
+ # The deadline of a test starting now.
27
+ def for_test(now = Limits.now) = @run.derive(now, @timeout && nanoseconds(@timeout))
28
+
29
+ # True once the run's budget is gone: nothing more gets dispatched.
30
+ def spent?(now = Limits.now) = @run.expired?(now)
31
+
32
+ # Why a test that reached deadline was cut short.
33
+ def message(deadline)
34
+ if @budget && deadline.expires_at == @run.expires_at
35
+ "run budget of #{@budget}s spent"
36
+ else
37
+ "timed out after #{@timeout}s"
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def nanoseconds(seconds) = (seconds * NS).round
44
+ end
45
+ end
@@ -53,11 +53,15 @@ module PaddedCell
53
53
  end
54
54
 
55
55
  def call(klass, name)
56
+ matches_name?(klass, name) && (!@selection.line_filtered? || line_selected?(klass, name))
57
+ end
58
+
59
+ def matches_name?(klass, name)
56
60
  id = "#{klass}##{name}"
57
61
  return false if @include && !matches?(@include, name, id)
58
62
  return false if @exclude && matches?(@exclude, name, id)
59
63
 
60
- !@selection.line_filtered? || line_selected?(klass, name)
64
+ true
61
65
  end
62
66
 
63
67
  private