• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mrexox / coverage-reporter / 5783799385

18 Jul 2023 08:44AM UTC coverage: 93.568% (+3.7%) from 89.828%
5783799385

push

github

mrexox
0.6.4: improve LCOV parsing speed

902 of 964 relevant lines covered (93.57%)

2.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.28
/src/coverage_reporter/cli/cmd.cr
1
require "../../coverage_reporter"
2
require "option_parser"
3
require "colorize"
4

5
module CoverageReporter::Cli
6
  extend self
7

8
  def run(args = ARGV, reporter = CoverageReporter::Reporter.new)
4✔
9
    opts = parse_args(args)
5✔
10
    greet(opts.no_logo?)
2✔
11

12
    reporter.configure(
2✔
13
      base_path: opts.base_path,
14
      carryforward: opts.carryforward,
15
      compare_ref: opts.compare_ref,
16
      compare_sha: opts.compare_sha,
17
      config_path: opts.config_path,
18
      coverage_files: opts.coverage_files.try(&.uniq),
3✔
19
      coverage_format: opts.format,
20
      dry_run: opts.dry_run?,
21
      fail_empty: !opts.allow_empty?,
22
      job_flag_name: opts.job_flag_name,
23
      overrides: opts.overrides,
2✔
24
      parallel: opts.parallel?,
25
      repo_token: opts.repo_token,
26
      measure: opts.debug? || opts.measure?
2✔
27
    )
28

29
    if opts.parallel_done?
3✔
30
      reporter.parallel_done
1✔
31
    else
32
      reporter.report
3✔
33
    end
34

35
    ok
36
  rescue ex : BaseException
37
    Log.error ex.message
1✔
38
    ex.fail? ? fail(opts) : ok
3✔
39
  rescue ex : Socket::Error
40
    Log.error ex.message
×
41
    fail(opts)
×
42
  rescue ex : ArgumentError
43
    Log.error <<-ERROR
×
44
    Oops! #{ex.message}
45
    Coveralls Coverage Reporter v#{CoverageReporter::VERSION}
×
46
    ERROR
47
    fail(opts)
2✔
48
  rescue ex : Api::InternalServerError
49
    Log.error "⚠️ Internal server error. Please contact Coveralls team."
1✔
50
    fail(opts)
1✔
51
  rescue ex : Api::UnprocessableEntity
52
    Log.error <<-ERROR
1✔
53
    ---
54
    Error: #{ex.message}
55
    Response: #{ex.response}
56
    ---
57
    🚨 Oops! It looks like your request was not processible by Coveralls.
58
    This is often the is the result of an incorrectly set repo token.
59
    More info/troubleshooting here: https://docs.coveralls.io
60
    - 💛, Coveralls
61
    ERROR
62
    fail(opts)
3✔
63
  rescue ex : Api::HTTPError
64
    Log.error <<-ERROR
1✔
65
    HTTP error:
66
    ---
67
    Error: #{ex.message} (#{ex.status_code})
68
    Message: #{ex.response}
69
    ---
70
    ERROR
71
    fail(opts)
1✔
72
  rescue ex
73
    raise(ex) if opts.try(&.debug?)
1✔
74

75
    Log.error ex.inspect
1✔
76
    fail(opts)
1✔
77
  end
78

79
  private class Opts
80
    property coverage_files : Array(String) | Nil
2✔
81
    property format : String?
2✔
82
    property repo_token : String?
×
83
    property base_path : String?
2✔
84
    property carryforward : String? = ENV["COVERALLS_CARRYFORWARD_FLAGS"]?.presence
3✔
85
    property job_flag_name : String? = ENV["COVERALLS_FLAG_NAME"]?.presence
4✔
86
    property config_path = CoverageReporter::YamlConfig::DEFAULT_LOCATION
1✔
87
    property compare_ref : String? = ENV["COVERALLS_COMPARE_REF"]?.presence
4✔
88
    property compare_sha : String? = ENV["COVERALLS_COMPARE_SHA"]?.presence
2✔
89

90
    # Flags
91
    property? no_logo = false
2✔
92
    property? parallel = !!(ENV["COVERALLS_PARALLEL"]?.presence && !ENV["COVERALLS_PARALLEL"].in?(["false", "0"]))
3✔
93
    property? parallel_done = false
2✔
94
    property? dry_run = false
2✔
95
    property? debug = false
×
96
    property? allow_empty = false
2✔
97
    property? measure = false
2✔
98
    property? no_fail = false
2✔
99

100
    # CI options overrides
101
    property service_name : String?
2✔
102
    property service_number : String?
2✔
103
    property service_job_id : String?
2✔
104
    property service_build_url : String?
2✔
105
    property service_job_url : String?
2✔
106
    property service_branch : String?
2✔
107
    property service_pull_request : String?
2✔
108
    property service_attempt : String?
2✔
109

110
    def overrides : CI::Options
2✔
111
      CI::Options.new(
1✔
112
        service_name: service_name,
113
        service_number: service_number,
114
        service_job_id: service_job_id,
115
        service_build_url: service_build_url,
116
        service_job_url: service_job_url,
117
        service_branch: service_branch,
118
        service_pull_request: service_pull_request,
119
        service_attempt: service_attempt,
120
      )
121
    end
122
  end
123

124
  private def parse_args(args, opts = Opts.new)
4✔
125
    option_parser = OptionParser.new do |parser|
2✔
126
      parser.banner = "Usage: coveralls [command] [options]"
1✔
127

128
      parser.on("report", "Report coverage") do
3✔
129
        parser.banner = "Usage: coveralls report [file reports] [options]"
1✔
130

131
        parser.on("--build-number=ID", "Build number") do |build_number|
3✔
132
          opts.service_number = build_number
1✔
133
        end
134

135
        parser.on(
1✔
136
          "-bPATH",
137
          "--base-path=PATH",
138
          "Path to the root folder of the project the coverage was collected in"
139
        ) do |path|
2✔
140
          opts.base_path = path
1✔
141
        end
142

143
        parser.on("-jFLAG", "--job-flag=FLAG", "Coverage job flag name, e.g. Unit Tests") do |flag|
3✔
144
          opts.job_flag_name = flag.presence
1✔
145
        end
146

147
        parser.on("-p", "--parallel", "Set the parallel flag. Requires webhook for completion (coveralls done)") do
3✔
148
          opts.parallel = true
1✔
149
        end
150

151
        parser.on("--format=FORMAT", "Force coverage file format, supported formats: #{Parser::PARSERS.map(&.name).join(", ")}") do |format|
2✔
152
          opts.format = format.presence
×
153
        end
154

155
        parser.on("--allow-empty", "Allow empty coverage results and exit 0") do
1✔
156
          opts.allow_empty = true
×
157
        end
158

159
        parser.on("--compare-ref=REF", "Git branch name to compare the coverage with") do |ref|
3✔
160
          opts.compare_ref = ref.presence
1✔
161
        end
162

163
        parser.on("--compare-sha=SHA", "Git commit SHA to compare the coverage with") do |sha|
1✔
164
          opts.compare_sha = sha.presence
×
165
        end
166

167
        parser.on("--service-name=NAME", "Build service name override") do |service_name|
3✔
168
          opts.service_name = service_name.presence
1✔
169
        end
170

171
        parser.on("--job-id=ID", "Build job override") do |job_id|
3✔
172
          opts.service_job_id = job_id.presence
1✔
173
        end
174

175
        parser.on("--build-url=URL", "Build URL override") do |build_url|
3✔
176
          opts.service_build_url = build_url.presence
1✔
177
        end
178

179
        parser.on("--job-url=URL", "Build job URL override") do |job_url|
3✔
180
          opts.service_job_url = job_url.presence
1✔
181
        end
182

183
        parser.on("--branch=NAME", "Branch name override") do |branch|
3✔
184
          opts.service_branch = branch.presence
1✔
185
        end
186

187
        parser.on("--pull-request=NUMBER", "PR number override") do |pull_request|
3✔
188
          opts.service_pull_request = pull_request.presence
1✔
189
        end
190

191
        parser.on("--attempt=NUMBER", "Run attempt number") do |attempt|
3✔
192
          opts.service_attempt = attempt
1✔
193
        end
194

195
        parser.on("-m", "--measure", "Measure time for parsing and HTTP requests") do
3✔
196
          opts.measure = true
1✔
197
        end
198

199
        parser.unknown_args do |unknown_args, after_dash_args|
3✔
200
          opts.coverage_files = unknown_args + after_dash_args
1✔
201
        end
202
      end
203

204
      parser.on("done", "Call a webhook after all parallel reports") do
3✔
205
        parser.banner = "Usage: coveralls done [options]"
1✔
206

207
        opts.parallel_done = true
1✔
208

209
        parser.on("--carryforward=FLAGS", "Comma-separated list of parallel job flags") do |flags|
3✔
210
          opts.carryforward = flags
1✔
211
        end
212

213
        parser.on("--build-number=ID", "Build number") do |build_number|
3✔
214
          opts.service_number = build_number
1✔
215
        end
216

217
        parser.on("--attempt", "Run attempt number") do |attempt|
1✔
218
          opts.service_attempt = attempt
×
219
        end
220

221
        parser.on("-m", "--measure", "Measure time for parsing and HTTP requests") do
3✔
222
          opts.measure = true
1✔
223
        end
224
      end
225

226
      parser.on("version", "Show version") do
1✔
227
        puts VERSION
×
228
        exit 0
×
229
      end
230

231
      parser.on("--debug", "Debug mode: data being sent to Coveralls will be printed to console") do
1✔
232
        opts.debug = true
×
233
        Log.set(Log::Level::Debug)
×
234
      end
235

236
      parser.on("--dry-run", "Dry run (no request sent)") do
3✔
237
        opts.dry_run = true
1✔
238
      end
239

240
      parser.on("--no-fail", "Always exit with status 0") do
3✔
241
        opts.no_fail = true
1✔
242
      end
243

244
      parser.on("-n", "--no-logo", "Do not show Coveralls logo in logs") do
3✔
245
        opts.no_logo = true
1✔
246
      end
247

248
      parser.on("-q", "--quiet", "Suppress all output") do
1✔
249
        Log.set(Log::Level::Error)
×
250
      end
251

252
      parser.on("-h", "--help", "Show this help") do
1✔
253
        # TODO: add environment variable notes
254
        puts "Coveralls Coverage Reporter v#{CoverageReporter::VERSION}"
×
255
        puts parser
×
256
        exit 0
×
257
      end
258

259
      parser.on(
1✔
260
        "-rTOKEN",
261
        "--repo-token=TOKEN",
262
        "Sets coveralls repo token, overrides settings in yaml or environment variable"
263
      ) do |token|
×
264
        opts.repo_token = token.presence
×
265
      end
266

267
      parser.on(
1✔
268
        "-cPATH",
269
        "--config-path=PATH",
270
        "Set the coveralls yaml config file location, will default to check '.coveralls.yml'"
271
      ) do |path|
×
272
        next unless path.presence
×
273

274
        opts.config_path = path
×
275
      end
276

277
      parser.on(
1✔
278
        "-bPATH",
279
        "--base-path=PATH",
280
        "DEPRECATED: Path to the root folder of the project the coverage was collected in"
281
      ) do |path|
×
282
        opts.base_path = path
×
283
      end
284

285
      parser.on("-fFILENAME", "--file=FILENAME", "DEPRECATED: Coverage artifact file to be reported, e.g. coverage/lcov.info (detected by default)") do |name|
2✔
286
        next if name.blank?
1✔
287

288
        opts.coverage_files = [name]
1✔
289
      end
290

291
      parser.on("-jFLAG", "--job-flag=FLAG", "DEPRECATED: Coverage job flag name, e.g. Unit Tests") do |flag|
1✔
292
        opts.job_flag_name = flag.presence
×
293
      end
294

295
      parser.on("-p", "--parallel", "DEPRECATED: Set the parallel flag. Requires webhook for completion (coveralls done)") do
1✔
296
        opts.parallel = true
×
297
      end
298

299
      parser.on("-d", "--done", "DEPRECATED: Call webhook after all parallel jobs (-p) done") do
3✔
300
        opts.parallel_done = true
1✔
301
      end
302

303
      parser.on("--format=FORMAT", "DEPRECATED: Force coverage file format, supported formats: #{Parser::PARSERS.map(&.name).join(", ")}") do |format|
4✔
304
        opts.format = format.presence
1✔
305
      end
306

307
      parser.on("--allow-empty", "DEPRECATED: Allow empty coverage results and exit 0") do
3✔
308
        opts.allow_empty = true
1✔
309
      end
310

311
      parser.on("--compare-ref=REF", "DEPRECATED: Git branch name to compare the coverage with") do |ref|
1✔
312
        opts.compare_ref = ref.presence
×
313
      end
314

315
      parser.on("--compare-sha=SHA", "DEPRECATED: Git commit SHA to compare the coverage with") do |sha|
1✔
316
        opts.compare_sha = sha.presence
×
317
      end
318

319
      parser.on("--carryforward=FLAGS", "DEPRECATED: Comma-separated list of parallel job flags") do |flags|
3✔
320
        opts.carryforward = flags
1✔
321
      end
322

323
      parser.on("--service-name=NAME", "DEPRECATED: Build service name override") do |service_name|
3✔
324
        opts.service_name = service_name.presence
1✔
325
      end
326

327
      parser.on("--service-job-id=ID", "DEPRECATED: Build job override") do |service_job_id|
3✔
328
        opts.service_job_id = service_job_id.presence
1✔
329
      end
330

331
      parser.on("--service-build-url=URL", "DEPRECATED: Build URL override") do |service_build_url|
3✔
332
        opts.service_build_url = service_build_url.presence
1✔
333
      end
334

335
      parser.on("--service-job-url=URL", "DEPRECATED: Build job URL override") do |service_job_url|
3✔
336
        opts.service_job_url = service_job_url.presence
1✔
337
      end
338

339
      parser.on("--service-branch=NAME", "DEPRECATED: Branch name override") do |service_branch|
3✔
340
        opts.service_branch = service_branch.presence
1✔
341
      end
342

343
      parser.on("--service-pull-request=NUMBER", "DEPRECATED: PR number override") do |service_pull_request|
3✔
344
        opts.service_pull_request = service_pull_request.presence
1✔
345
      end
346

347
      parser.on("-v", "--version", "DEPRECATED: Show version") do
1✔
348
        puts VERSION
×
349
        exit 0
×
350
      end
351
    end
352

353
    option_parser.parse(args)
1✔
354

355
    opts
356
  rescue ex : OptionParser::InvalidOption
357
    puts "⚠️ #{ex.message}"
×
358
    puts
×
359
    puts option_parser
×
360
    exit 1
×
361
  end
362

363
  private def ok
364
    0
365
  end
366

367
  private def fail(opts)
1✔
368
    return 1 unless opts
1✔
369
    return ok if opts.no_fail?
1✔
370

371
    1
372
  end
373

374
  private def greet(no_logo : Bool)
1✔
375
    Colorize.on_tty_only!
1✔
376

377
    if no_logo
1✔
378
      Log.info "⭐️ Coveralls.io Coverage Reporter v#{CoverageReporter::VERSION}"
1✔
379
    else
380
      Log.info " "
1✔
381
      Log.info "⠀⠀⠀⠀⠀⠀#{"⣿".colorize(Log::RED)}"
1✔
382
      Log.info "⠀⠀⠀⠀⠀#{"⣼⣿⣧".colorize(Log::RED)}⠀⠀⠀⠀⠀⠀⠀ ⣠⣶⣾⣿⡇⢀⣴⣾⣿⣷⣆ ⣿⣿⠀⣰⣿⡟⢸⣿⣿⣿⡇ ⣿⣿⣿⣷⣦⠀⠀⢠⣿⣿⣿⠀⠀⣿⣿⠁⠀⣼⣿⡇⠀⢀⣴⣾⣿⡷"
1✔
383
      Log.info "#{"⠶⣶⣶⣶⣾⣿⣿⣿⣷⣶⣶⣶⠶".colorize(Log::RED)}  ⣸⣿⡟ ⠀⢠⣿⣿⠃⠈⣿⣿⠀⣿⣿⢠⣿⡿⠀⣿⣿⣧⣤⠀⢸⣿⡇⣠⣿⡿⠀⢠⣿⡟⣿⣿⠀⢸⣿⡿⠀⠀⣿⣿⠃⠀⢸⣿⣧⣄"
1✔
384
      Log.info "⠀⠀#{"⠙⢻⣿⣿⣿⣿⣿⡟⠋⠁".colorize(Log::RED)}⠀⠀ ⣿⣿⡇⠀ ⢸⣿⣿⠀⣸⣿⡟⠀⣿⣿⣾⡿⠁ ⣿⣿⠛⠛⠀⣿⣿⢿⣿⣏⠀⢀⣿⣿⣁⣿⣿⠀⣾⣿⡇⠀⢸⣿⡿⠀⠀⡀⠙⣿⣿⡆"
1✔
385
      Log.info "⠀⠀#{"⢠⣿⣿⣿⠿⣿⣿⣿⡄".colorize(Log::RED)}⠀⠀⠀ ⠙⢿⣿⣿⠇⠈⠿⣿⣿⡿⠋⠀⠀⢿⣿⡿⠁⠀⢸⣿⣿⣿⡇⢸⣿⣿⠀⣿⣿⣄⣾⣿⠛⠛⣿⣿⢠⣿⣿⣿ ⣼⣿⣿⣿ ⣿⣿⡿⠋⠀"
1✔
386
      Log.info "⠀#{"⢀⣾⠟⠋⠀⠀⠀⠙⠻⣷⡀".colorize(Log::RED)}⠀⠀"
1✔
387
      Log.info " "
1✔
388
      Log.info "  v#{CoverageReporter::VERSION}\n\n"
1✔
389
    end
390
  end
391
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc