Module: SimpleCov::CLI::Tests

Extended by:
CommandHelpers, Tests
Included in:
Tests
Defined in:
lib/simplecov/cli/tests.rb,
lib/simplecov/cli/tests/redundancy.rb

Overview

simplecov tests [<path>[:<line>]]: list the recorded tests from a coverage.json written under track_tests. Text output is one id per line so the list can feed a test runner directly; empty answers keep stdout empty and note the emptiness on stderr instead.

Defined Under Namespace

Modules: Redundancy

Constant Summary

Constants included from CommandHelpers

CommandHelpers::STATS_ROW_FORMAT

Instance Method Summary collapse

Methods included from CommandHelpers

build_parser, command_name, common_options, error, error_nil, on_help, one?, parse_common, quiet_option, recorded_contexts, stats_row

Instance Method Details

#context_table(entry, contexts, opts, stderr) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/simplecov/cli/tests.rb', line 92

def context_table(entry, contexts, opts, stderr)
  raw = entry["contexts"] || {}
  table = decode_table(raw, contexts.size) if raw.is_a?(Hash)
  return table if table

  CoverageFile.report_invalid(stderr, "tests", opts.fetch(:input),
    "entry for #{opts.fetch(:path)} carries a malformed \"contexts\" table")
end

#decode_table(raw, context_count) ⇒ Object

An absent key is an untouched file; anything malformed, a foreign index or a non-hex bitmap, poisons the whole answer, matching the all-or-nothing tolerance the resultset reader applies.



104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/simplecov/cli/tests.rb', line 104

def decode_table(raw, context_count)
  table = {} #: Hash[Integer, Integer]
  raw.each do |index, hex|
    return nil unless index.is_a?(String) && index.match?(/\A\d+\z/)
    return nil unless hex.is_a?(String) && hex.match?(/\A\h+\z/)

    position = Integer(index, 10)
    return nil unless position < context_count

    table[position] = Integer(hex, 16)
  end
  table
end

#emit(ids, opts, stdout, stderr) ⇒ Object



123
124
125
126
127
128
# File 'lib/simplecov/cli/tests.rb', line 123

def emit(ids, opts, stdout, stderr)
  return stdout.puts(JSON.pretty_generate(ids)) if opts.fetch(:json)
  return note_empty(opts, stderr) if ids.empty?

  ids.each { |id| stdout.puts(id) }
end

#empty_message(opts) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/simplecov/cli/tests.rb', line 134

def empty_message(opts)
  target = opts.fetch(:target)
  if opts[:redundant] && !opts.fetch(:no_recording)
    return "no redundant test covers #{target}" if target

    "no redundant tests, every recorded test covers at least one line uniquely"
  elsif target
    "no recorded test covers #{target}"
  else
    "no tests recorded"
  end
end

#ids_from(table, contexts, line) ⇒ Object



118
119
120
121
# File 'lib/simplecov/cli/tests.rb', line 118

def ids_from(table, contexts, line)
  bit = line && (1 << (line - 1))
  table.filter_map { |index, bitmap| contexts.fetch(index) if bit.nil? || bitmap.anybits?(bit) }.sort
end

#locate_entry(document, opts, stderr) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/simplecov/cli/tests.rb', line 77

def locate_entry(document, opts, stderr)
  coverage = document["coverage"]
  input = opts.fetch(:input)
  unless coverage.is_a?(Hash)
    return CoverageFile.report_invalid(stderr, "tests", input, '"coverage" must be an object')
  end

  path = opts.fetch(:path)
  match = CoverageFile.lookup(coverage, path)
  return error_nil(stderr, CoverageFile.not_found_message(coverage, path, input)) unless match
  return match.last if match.last.is_a?(Hash)

  CoverageFile.report_invalid(stderr, "tests", input, "entry for #{path} must be an object")
end

#note_empty(opts, stderr) ⇒ Object



130
131
132
# File 'lib/simplecov/cli/tests.rb', line 130

def note_empty(opts, stderr)
  stderr.puts("simplecov tests: #{empty_message(opts)}")
end

#parse(args) ⇒ Object



33
34
35
36
37
38
39
40
41
42
# File 'lib/simplecov/cli/tests.rb', line 33

def parse(args)
  opts, rest = parse_common(args) do |parser, options|
    parser.on("--redundant") { options[:redundant] = true }
  end
  opts[:target] = rest.first
  split = split_target(rest.first)
  opts[:path] = split.fetch(:path)
  opts[:line] = split.fetch(:line)
  opts
end

#resolve(document, opts, stderr) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/simplecov/cli/tests.rb', line 55

def resolve(document, opts, stderr)
  contexts = recorded_contexts(document, opts, stderr)
  return unless contexts

  opts[:no_recording] = contexts.empty?
  ids = selected_ids(document, contexts, opts, stderr)
  return ids unless ids && opts[:redundant]

  redundant = Redundancy.redundant_ids(document, contexts, opts, stderr)
  redundant && (ids & redundant)
end

#run(args, stdout:, stderr:) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/simplecov/cli/tests.rb', line 19

def run(args, stdout:, stderr:)
  opts = parse(args)
  return error(stderr, "line number must be positive") if opts.fetch(:line)&.zero?

  document = CoverageFile.load_document(opts.fetch(:input), command: "tests", stderr: stderr)
  return 1 unless document

  ids = resolve(document, opts, stderr)
  return 1 unless ids

  emit(ids, opts, stdout, stderr)
  0
end

#selected_ids(document, contexts, opts, stderr) ⇒ Object



67
68
69
70
71
72
73
74
75
# File 'lib/simplecov/cli/tests.rb', line 67

def selected_ids(document, contexts, opts, stderr)
  return contexts.sort unless opts.fetch(:path)

  entry = locate_entry(document, opts, stderr)
  return unless entry

  table = context_table(entry, contexts, opts, stderr)
  table && ids_from(table, contexts, opts.fetch(:line))
end

#split_target(target) ⇒ Object

Only an all-digit tail after the last colon reads as a line number, so Windows drive colons and exotic filenames stay plain paths.



46
47
48
49
50
51
52
53
# File 'lib/simplecov/cli/tests.rb', line 46

def split_target(target)
  return {path: nil, line: nil} unless target

  path, sep, tail = target.rpartition(":")
  return {path: target, line: nil} if sep.empty? || !tail.match?(/\A\d+\z/)

  {path: path, line: Integer(tail, 10)}
end