Skip to content

Commit

Permalink
Implement dumper/parser for example statuses.
Browse files Browse the repository at this point in the history
  • Loading branch information
myronmarston committed Mar 10, 2015
1 parent bd774ef commit 0529b6e
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 0 deletions.
103 changes: 103 additions & 0 deletions lib/rspec/core/example_status_persister.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
module RSpec
module Core
class ExampleStatusPersister
end

# Dumps a list of hashes in a pretty, human readable format
# for later parsing. The hashes are expected to have symbol
# keys and string values, and each hash should have the same
# set of keys.
# @private
class ExampleStatusDumper
def self.dump(examples)
new(examples).dump
end

def initialize(examples)
@examples = examples
end

def dump
return nil if @examples.empty?
(formatted_header_rows + formatted_value_rows).join("\n") << "\n"
end

private

def formatted_header_rows
@formatted_header_rows ||= begin
dividers = column_widths.map { |w| "-" * w }
[formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)]
end
end

def formatted_value_rows
@foramtted_value_rows ||= rows.map do |row|
formatted_row_from(row)
end
end

def rows
@rows ||= @examples.map { |ex| ex.values_at(*headers) }
end

def formatted_row_from(row_values)
padded_values = row_values.each_with_index.map do |value, index|
value.ljust(column_widths[index])
end

padded_values.join(" | ") << " |"
end

def headers
@headers ||= @examples.first.keys
end

def column_widths
@column_widths ||= begin
value_sets = rows.transpose

headers.each_with_index.map do |header, index|
values = value_sets[index] << header.to_s
values.map(&:length).max
end
end
end
end

# Parses a string that has been previously dumped by ExampleStatusDumper.
# Note that this parser is a bit naive in that it does a simple split on
# "\n" and " | ", with no concern for handling escaping. For now, that's
# OK because the values we plan to persist (example id, status, and perhaps
# example duration) are highly unlikely to contain "\n" or " | " -- after
# all, who puts those in file names?
# @private
class ExampleStatusParser
def self.parse(string)
new(string).parse
end

def initialize(string)
@header_line, _, *@row_lines = string.lines.to_a
end

def parse
@row_lines.map { |line| parse_row(line) }
end

private

def parse_row(line)
Hash[headers.zip(split_line(line))]
end

def headers
@headers ||= split_line(@header_line).map(&:to_sym)
end

def split_line(line)
line.split(/\s+\|\s+/)
end
end
end
end
88 changes: 88 additions & 0 deletions spec/rspec/core/example_status_persister_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require 'rspec/core/example_status_persister'

module RSpec::Core
RSpec.describe "Example status serialization" do
it 'serializes the provided example statuses in a human readable format' do
examples = [
{ :example_id => "./spec/unit/foo_spec.rb[1:1]", :status => 'passed' },
{ :example_id => "./spec/unit/foo_spec.rb[1:2]", :status => 'pending' },
{ :example_id => "./spec/integration/foo_spec.rb[1:2]", :status => 'failed' }
]

produce_expected_output = eq(unindent(<<-EOS))
example_id | status |
----------------------------------- | ------- |
./spec/unit/foo_spec.rb[1:1] | passed |
./spec/unit/foo_spec.rb[1:2] | pending |
./spec/integration/foo_spec.rb[1:2] | failed |
EOS

if RUBY_VERSION == '1.8.7' # unordered hashes :(.
produce_expected_output |= eq(unindent(<<-EOS))
status | example_id |
------- | ----------------------------------- |
passed | ./spec/unit/foo_spec.rb[1:1] |
pending | ./spec/unit/foo_spec.rb[1:2] |
failed | ./spec/integration/foo_spec.rb[1:2] |
EOS
end

expect(dump(examples)).to produce_expected_output
end

it 'takes the column headers into account when sizing the columns' do
examples = [
{ :long_key => '12', :a => '20' },
{ :long_key => '120', :a => '2' }
]

produce_expected_output = eq(unindent(<<-EOS))
long_key | a |
-------- | -- |
12 | 20 |
120 | 2 |
EOS

if RUBY_VERSION == '1.8.7' # unordered hashes :(.
produce_expected_output |= eq(unindent(<<-EOS))
a | long_key |
-- | -------- |
20 | 12 |
2 | 120 |
EOS
end

expect(dump(examples)).to produce_expected_output
end

it 'can round trip through the dumper and parser' do
examples = [
{ :example_id => "./spec/unit/foo_spec.rb[1:1]", :status => 'passed' },
{ :example_id => "./spec/unit/foo_spec.rb[1:2]", :status => 'pending' },
{ :example_id => "./spec/integration/foo_spec.rb[1:2]", :status => 'failed' }
]

round_tripped = parse(dump(examples))
expect(round_tripped).to eq(examples)
end

it 'produces nothing when given nothing' do
expect(dump([])).to eq(nil)
end

# Intended for use with indented heredocs.
# taken from Ruby Tapas:
# https://rubytapas.dpdcart.com/subscriber/post?id=616#files
def unindent(s)
s.gsub(/^#{s.scan(/^[ \t]+(?=\S)/).min}/, "")
end

def dump(examples)
ExampleStatusDumper.dump(examples)
end

def parse(string)
ExampleStatusParser.parse(string)
end
end
end

0 comments on commit 0529b6e

Please sign in to comment.