Skip to content

Commit

Permalink
Add output format representation
Browse files Browse the repository at this point in the history
Upcoming commits will add a progressive reporter that does NOT require a
rewindable output. Usefull for imperfect terminal emulations as on CI
etc.
  • Loading branch information
mbj committed Aug 10, 2014
1 parent 3654ba0 commit ac8fe85
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 85 deletions.
5 changes: 4 additions & 1 deletion lib/mutant.rb
Expand Up @@ -17,6 +17,7 @@
require 'concord'
require 'morpher'
require 'parallel'
require 'open3'

# Library namespace
module Mutant
Expand Down Expand Up @@ -190,6 +191,8 @@ def inspect
require 'mutant/reporter/trace'
require 'mutant/reporter/cli'
require 'mutant/reporter/cli/printer'
require 'mutant/reporter/cli/tput'
require 'mutant/reporter/cli/format'
require 'mutant/zombifier'
require 'mutant/zombifier/file'

Expand All @@ -204,7 +207,7 @@ class Config
includes: EMPTY_ARRAY,
requires: EMPTY_ARRAY,
isolation: Mutant::Isolation::Fork,
reporter: Reporter::CLI.new($stdout),
reporter: Reporter::CLI.build($stdout),
zombie: false,
processes: Parallel.processor_count,
expected_coverage: 100.0
Expand Down
10 changes: 10 additions & 0 deletions lib/mutant/reporter.rb
Expand Up @@ -13,6 +13,16 @@ class Reporter
#
abstract_method :warn

# Report start
#
# @param [Env] env
#
# @return [self]
#
# @api private
#
abstract_method :start

# Report collector state
#
# @param [Runner::Collector] collector
Expand Down
117 changes: 41 additions & 76 deletions lib/mutant/reporter/cli.rb
Expand Up @@ -2,47 +2,50 @@ module Mutant
class Reporter
# Reporter that reports in human readable format
class CLI < self
include Concord.new(:output)
include Concord.new(:output, :format)

NL = "\n".freeze
CLEAR_PREV_LINE = "\e[1A\e[2K".freeze

# Output abstraction to decouple tty? from buffer
class Output
include Concord.new(:tty, :buffer)
# Build reporter
#
# @param [IO] output
#
# @return [Reporter::CLI]
#
# @api private
#
def self.build(output)
ci = ENV.key?('CI')
tty = output.respond_to?(:tty?) && output.tty?
format = Format::Framed.new(
tty: tty,
tput: Tput::INSTANCE,
)

# Test if output is a tty
#
# @return [Boolean]
# Upcoming commits implementing progressive format will change this to
# the equivalent of:
#
# @api private
#
def tty?
@tty
end

[:puts, :write].each do |name|
define_method(name) do |*args, &block|
buffer.public_send(name, *args, &block)
end
end

end # Output

# Rate per second progress report fires
OUTPUT_RATE = 1.0 / 20
# if !ci && tty && Tput::INSTANCE.available
# Format::Framed.new(
# tty: tty,
# tput: Tput::INSTANCE,
# )
# else
# Format::Progressive.new(
# tty: tty,
# )
# end

new(output, format)
end

# Initialize object
# Report start
#
# @return [undefined]
# @param [Env] env
#
# @api private
#
def initialize(*)
super
@last_frame = nil
@last_length = 0
@tty = output.respond_to?(:tty?) && output.tty?
def start(env)
write(format.start(env))
self
end

# Report progress object
Expand All @@ -54,8 +57,8 @@ def initialize(*)
# @api private
#
def progress(collector)
throttle do
swap(frame(Printer::Collector, collector))
format.throttle do
write(format.progress(collector))
end

self
Expand Down Expand Up @@ -83,60 +86,22 @@ def warn(message)
# @api private
#
def report(env)
swap(frame(Printer::EnvResult, env))
write(format.report(env))
self
end

private

# Compute progress frame
#
# @return [String]
#
# @api private
#
def frame(reporter, object)
buffer = StringIO.new
buffer.write(clear_command) if @tty
reporter.run(Output.new(@tty, buffer), object)
buffer.rewind
buffer.read
end

# Swap output frame
# Write output frame
#
# @param [String] frame
#
# @return [undefined]
#
# @api private
#
def swap(frame)
def write(frame)
output.write(frame)
@last_length = frame.split(NL).length
end

# Call block throttled
#
# @return [undefined]
#
# @api private
#
def throttle
now = Time.now
return if @last_frame && (now - @last_frame) < OUTPUT_RATE
yield
@last_frame = now
end

# Return clear command for last frame length
#
# @return [String]
#
# @api private
#
def clear_command
CLEAR_PREV_LINE * @last_length
end

end # CLI
Expand Down
157 changes: 157 additions & 0 deletions lib/mutant/reporter/cli/format.rb
@@ -0,0 +1,157 @@
module Mutant
class Reporter
class CLI
# CLI output format
class Format
include AbstractType, Anima.new(:tty)

# Return progress representation
#
# @param [Runner::Collector] collector
#
# @return [String]
#
# @api private
#
abstract_method :progress

# Throttle report execution
#
# @return [self]
#
# @api private
#
abstract_method :throttle

# Format result
#
# @param [Result::Env] env
#
# @return [String]
#
# @api private
#
def report(env)
format(Printer::EnvResult, env)
end

# Output abstraction to decouple tty? from buffer
class Output
include Concord.new(:tty, :buffer)

# Test if output is a tty
#
# @return [Boolean]
#
# @api private
#
def tty?
@tty
end

[:puts, :write].each do |name|
define_method(name) do |*args, &block|
buffer.public_send(name, *args, &block)
end
end

end # Output

private

# Format object with printer
#
# @param [Class:Printer] printer
# @param [Object] object
#
# @return [String]
#
# @api private
#
def format(printer, object)
buffer = new_buffer
printer.run(Output.new(tty, buffer), object)
buffer.rewind
buffer.read
end

# Format for framed rewindable output
class Framed < self
include anima.add(:tput)

BUFFER_FLAGS = 'a+'.freeze

# Rate per second progress report fires
OUTPUT_RATE = 1.0 / 20

# Initialize object
#
# @return [undefined]
#
# @api private
#
def initialize(*)
super
@last_frame = nil
end

# Format start
#
# @param [Env] env
#
# @return [String]
#
# @api private
#
def start(_env)
tput.prepare
end

# Format progress
#
# @param [Runner::Collector] collector
#
# @return [String]
#
# @api private
#
def progress(collector)
format(Printer::Collector, collector)
end

# Call block throttled
#
# @return [self]
#
# @api private
#
def throttle
now = Time.now
return if @last_frame && (now - @last_frame) < OUTPUT_RATE
yield
@last_frame = now
self
end

private

# Return new buffer
#
# @return [StringIO]
#
# @api private
#
def new_buffer
# For some reason this raises an Ernno::EACCESS errror:
#
# StringIO.new(Tput::INSTANCE.restore, BUFFER_FLAGS)
#
buffer = StringIO.new
buffer << tput.restore
end

end # Framed
end # Format
end # CLI
end # Reporter
end # Mutant
2 changes: 2 additions & 0 deletions lib/mutant/reporter/cli/printer.rb
Expand Up @@ -5,6 +5,8 @@ class CLI
class Printer
include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object)

NL = "\n".freeze

# Run printer on object to output
#
# @param [IO] output
Expand Down

0 comments on commit ac8fe85

Please sign in to comment.