Module: SimpleCov::CLI::Serve

Extended by:
CommandHelpers
Defined in:
lib/simplecov/cli/serve.rb,
lib/simplecov/cli/serve/report_preparer.rb,
lib/simplecov/cli/serve/static_file_handler.rb

Overview

simplecov serve [--port N] [--host HOST] — serve the coverage report over HTTP. A small static file server backed by stdlib socket, so there's no extra dependency just for "view a local report on a CI box where file:// doesn't work." This module is the CLI wiring (options, binding, the accept loop); the HTTP mechanics live in StaticFileHandler.

Defined Under Namespace

Modules: ReportPreparer, StaticFileHandler

Constant Summary

Constants included from CommandHelpers

CommandHelpers::STATS_ROW_FORMAT

Class Method Summary collapse

Methods included from CommandHelpers

command_name, common_options, error, parse_common, stats_row

Class Method Details

.announce(stdout, server, dir) ⇒ Object



60
61
62
63
64
65
# File 'lib/simplecov/cli/serve.rb', line 60

def announce(stdout, server, dir)
  port = server.addr[1]
  host = url_host(server.addr[3])
  stdout.puts("simplecov serve: serving #{dir} at http://#{host}:#{port}/")
  stdout.puts("Press Ctrl-C to stop.")
end

.parse(args) ⇒ Object



51
52
53
54
55
56
57
58
# File 'lib/simplecov/cli/serve.rb', line 51

def parse(args)
  opts = {port: 0, host: "127.0.0.1"} #: Hash[Symbol, untyped]
  OptionParser.new do |o|
    o.on("--port N", Integer) { |v| opts[:port] = v }
    o.on("--host HOST")       { |v| opts[:host] = v }
  end.parse(args)
  opts
end

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



21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/simplecov/cli/serve.rb', line 21

def run(args, stdout:, stderr:, **)
  opts = parse(args)
  dir = SimpleCov::CLI.coverage_dir
  message = ReportPreparer.call(dir)
  return error(stderr, message) if message

  require "socket"
  with_server(opts, stderr) do |server|
    announce(stdout, server, dir)
    serve_loop(server, dir, stdout)
    0
  end
end

.serve_loop(server, dir, stdout) ⇒ Object

One thread per connection: browsers open speculative sockets that send no bytes, and a serial loop would stall every real request (the report's assets included) behind them.



76
77
78
79
80
81
82
# File 'lib/simplecov/cli/serve.rb', line 76

def serve_loop(server, dir, stdout)
  loop do
    Thread.new(server.accept) { |client| StaticFileHandler.handle_connection(client, dir) }
  end
rescue Interrupt
  stdout.puts("\nsimplecov serve: stopping")
end

.url_host(host) ⇒ Object

IPv6 literals need brackets in a URL (--host ::1 must announce http://[::1]:PORT/, not the invalid http://::1:PORT/).



69
70
71
# File 'lib/simplecov/cli/serve.rb', line 69

def url_host(host)
  host.include?(":") ? "[#{host}]" : host
end

.with_server(opts, stderr) ⇒ Object

Returns the block's exit status, or 1 when the socket can't be bound (port already taken, privileged port, unresolvable host).



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/simplecov/cli/serve.rb', line 37

def with_server(opts, stderr)
  # The receiver cast works around an rbs stdlib gap: TCPSocket's
  # explicit `self.new` shadows TCPServer#initialize's (host, port) form.
  server = (_ = TCPServer).new(opts[:host], opts[:port]) #: TCPServer
rescue SystemCallError, SocketError => e
  error(stderr, "cannot bind to #{opts[:host]}:#{opts[:port]} (#{e.message})")
else
  begin
    yield server
  ensure
    server.close
  end
end