Module: SimpleCov::CLI::Serve

Extended by:
CommandHelpers, Serve
Included in:
Serve
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: a small static file server backed by stdlib socket, so there is no extra dependency just for viewing a local report on a box where file:// doesn't work. This module is the CLI wiring; the HTTP mechanics live in StaticFileHandler.

Defined Under Namespace

Modules: ReportPreparer, StaticFileHandler

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

#announce(stdout, server, dir) ⇒ Object



64
65
66
67
68
69
# File 'lib/simplecov/cli/serve.rb', line 64

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

#parse(args) ⇒ Object



55
56
57
58
59
60
61
62
# File 'lib/simplecov/cli/serve.rb', line 55

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

#require_socketObject

Required here rather than at load time so the other subcommands do not pay for sockets.



35
36
37
# File 'lib/simplecov/cli/serve.rb', line 35

def require_socket
  require "socket"
end

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



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

def run(args, stdout:, stderr:, **)
  opts = parse(args)
  dir = 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 behind them.



79
80
81
82
83
84
85
# File 'lib/simplecov/cli/serve.rb', line 79

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/.



73
74
75
# File 'lib/simplecov/cli/serve.rb', line 73

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

#with_server(opts, stderr) ⇒ Object

Answers 1 when the socket can't be bound: port already taken, privileged port, unresolvable host. The receiver cast works around an rbs stdlib gap, where TCPSocket's explicit self.new shadows TCPServer#initialize's (host, port) form.



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/simplecov/cli/serve.rb', line 43

def with_server(opts, stderr)
  server = (_ = TCPServer).new(opts.fetch(:host), opts.fetch(:port)) #: TCPServer
rescue SystemCallError, SocketError => e
  error(stderr, "cannot bind to #{opts.fetch(:host)}:#{opts.fetch(:port)} (#{e})")
else
  begin
    yield server
  ensure
    server.close
  end
end