RSpec formatters 2
Our very first contest challenged the contestants to create new formatters for the RSpec testing library. After it closed, I received a lot of comments from people that missed the contest but would have loved to participate.
We decided to do it again. The rules are the same as last time:
This week, the challenge is to create your own formatter for RSpec 2. Your solution should solve a problem you’re facing with the existing formatters (like, I don’t know how long my specs are going to take or I don’t notice when my suite is done running) or you can do something completely crazy and funny. With rainbows, or something like that. Oh, and remember: You’re not limited to terminal output, do whatever you can think of.
A good starting point would be to check out the existing formatters. Also, don’t forget to take a look at the entries in the previous RSpec formatters contest.
As always, when you’re done, put your solution in a Gist, including a README file to explain what it does, how it works and why it should win. Of course, you’re encouraged to put a link to a demo video of your formatter in action in your Gist too.
Prize
This week’s winner gets to choose between $25 of 6sync hosting credit or one month of free Github Micro!
-
Finished in 1st place with a final score of 4.3/5. (View the Gist)README.md
Chuck testr RSpec formatter
This formatter is a game changer... Nope! It's just Chuck TestaR
Requirements
It requires
rspecandgrowl_notifygem. It works best on OS X.Installation
Since we're using growl notifications you may want to download two additional images (not included in the gist):
curl http://img.skitch.com/20111031-8x26je3q4x41gy17qhh63j539f.png > chuck-normal.png curl http://img.skitch.com/20111031-gmsuuk85ti7tircttd1237u7u7.png > chuck-nope.pngAfter that, you're good to go!
Usage
$ rspec -r ./chuck_testar.rb -f ChuckTestar -c chuck_testar_spec.rbportable.rbmodule Portable def self.platform if RbConfig::CONFIG['host_os'] =~ /mswin|windows|cygwin/i 'windows' elsif RbConfig::CONFIG['host_os'] =~ /darwin/ 'osx' else 'linux' end end end
chuck_testar.rbrequire 'rspec/core/formatters/base_text_formatter' require './notifications' require './portable' class ChuckTestar < RSpec::Core::Formatters::BaseTextFormatter def icon(name) File.join(File.dirname(__FILE__), name) end def notify(text, icon_filename) if Object.const_defined? "GrowlNotify" GrowlNotify.normal({ :title => 'RSpec', :description => text, :icon => icon(icon_filename) }) end end def say(text) if Portable.platform == 'linux' `echo "#{text}" | espeak` elsif Portable.platform == 'osx' `say "#{text}"` end end def notify_with_voice!(text, icon_filename) notify(text, icon_filename) say(text) end def delay(seconds) sleep(seconds) end def start(example_count) super(example_count) output.print green('Y') end def stop output.print green('p') output.print green("\n\nYour tests pass!\n") notify_with_voice!('Your tests pass', 'chuck-normal.png') delay(2) if @failed_examples.length > 0 output.print magenta("\n\nNope! It's just Chuck Testa!") notify_with_voice!("Nope! It\'s just Chuck Testa!", 'chuck-nope.png') delay(1) end end def example_passed(example) super(example) output.print green('e') end def example_pending(example) super(example) output.print green('e') end def example_failed(example) super(example) output.print green('e') end def start_dump super() output.puts end end
notifications.rbbegin require "growl_notify" GrowlNotify.config do |config| config.notifications = ["Chuck Testar"] config.default_notifications = ["Chuck Testar"] config.application_name = "Chuck Testar RSpec formatter" # this shoes up in the growl applications list in systems settings end rescue LoadError puts "Please install growl_notify gem to enable growl notifications" end
chuck_testar_spec.rbView full entryrequire './chuck_testar' describe ChuckTestar do let(:output) { StringIO.new } let(:formatter) { ChuckTestar.new(output) } before do formatter.stub(:delay => true) end describe "output" do let(:example) { double("example 1", :execution_result => {:status => 'failed', :exception => Exception.new } ) } it "displays 'Y' at the start of the suite" do formatter.start(1) output.string.should =~ /Y/ end it "displays 'e' when examples pass" do formatter.example_passed(example) output.string.should =~ /e/ end it "displays 'e' when examples fails" do formatter.example_failed(example) output.string.should =~ /e/ end it "displays 'p' at the end of tests suite" do formatter.stub(:notify_with_voice! => true) formatter.stop output.string.should =~ /p/ end end describe "an failing example" do it "fails" do false.should be_true end end describe "notifications" do before do formatter.stub(:say => true) end it "display success notification when spec pass" do formatter.example_passed(example) GrowlNotify.should_receive(:normal).with(:title => 'RSpec', :description => 'Your tests pass', :icon => formatter.icon('chuck-normal.png')) formatter.stop end it "display success and fail notifications when spec fail" do formatter.example_failed(example) GrowlNotify.should_receive(:normal).with(:title => 'RSpec', :description => 'Your tests pass', :icon => formatter.icon('chuck-normal.png')) GrowlNotify.should_receive(:normal).with(:title => 'RSpec', :description => "Nope! It's just Chuck Testa!", :icon => formatter.icon('chuck-nope.png')) formatter.stop end end end
-
Finished in 2nd place with a final score of 3.8/5. (View the Gist)README.md
Nyan Cat RSpec Formatter!
-_-_-_-_-_-_-_,------, _-_-_-_-_-_-_-| /\_/\ -_-_-_-_-_-_-~|__( ^ .^) _-_-_-_-_-_-_-"" ""This is my take on the Nyan Cat RSpec Formatter. It simply creates a rainbow trail of test results. It also counts the number of examples as they execute and highlights failed and pending specs.
The rainbow changes colors as it runs. You must try it:
rspec nyan_formatter_spec.rb --require ./nyan_formatter.rb --format NyanFormatter(Scroll down for screenshots)
rspec_nyan_cat_formatter.png
rspec_nyan_cat_formatter_failing.png
nyan_formatter_spec.rbrequire 'stringio' require File.join(File.dirname(__FILE__), 'nyan_formatter.rb') describe NyanFormatter do before do @output = StringIO.new @formatter = NyanFormatter.new(@output) @formatter.start(2) @example = RSpec::Core::ExampleGroup.describe.example sleep(0.2) # Just to slow it down a little :-) end describe 'passed, pending and failed' do before do @formatter.stub!(:tick) end describe 'example_passed' do it 'should call the increment method' do @formatter.should_receive :tick @formatter.example_passed(@example) end it 'should relax Nyan Cat' do @formatter.example_passed(@example) @formatter.nyan_cat.should == '~|_(^.^)' end end describe 'example_pending' do it 'should call the tick method' do @formatter.should_receive :tick @formatter.example_pending(@example) end it 'should increment the pending count' do lambda { @formatter.example_pending(@example)}. should change(@formatter, :pending_count).by(1) end it 'should alert Nyan Cat' do @formatter.example_pending(@example) @formatter.nyan_cat.should == '~|_(o.o)' end end describe 'example_failed' do it 'should call the increment method' do @formatter.should_receive :tick @formatter.example_failed(@example) end it 'should increment the failure count' do lambda { @formatter.example_failed(@example)}. should change(@formatter, :failure_count).by(1) end it 'should alert Nyan Cat' do @formatter.example_failed(@example) @formatter.nyan_cat.should == '~|_(o.o)' end end end describe 'tick' do before do @formatter.stub!(:current).and_return(1) @formatter.stub!(:example_count).and_return(2) @formatter.tick end it 'should change title' do @formatter.title.should == ' 1/2' end it 'should calculate the percentage done' do @formatter.percentage.should == 50 end it 'should increment the current' do @formatter.current.should == 1 end it 'should store the marks in an array' do @formatter.example_results.should include('=') end end describe 'rainbowify' do it 'should increment the color index count' do lambda { @formatter.rainbowify('=') }.should change(@formatter, :color_index).by(1) end end describe 'highlight' do it 'should rainbowify passing examples' do @formatter.highlight('=').should == "\e[38;5;154m=\e[0m" end it 'should mark failing examples as red' do @formatter.highlight('*').should == "\e[31m*\e[0m" end it 'should mark pending examples as yellow' do @formatter.highlight('!').should == "\e[33m!\e[0m" end end describe 'start' do it 'should set the total amount of specs' do @formatter.example_count.should == 2 end it 'should set the current to 0' do @formatter.current.should == 0 end end end
nyan_formatter.rbView full entry# -*- coding: utf-8 -*- require 'rspec/core/formatters/base_text_formatter' class NyanFormatter < RSpec::Core::Formatters::BaseTextFormatter ESC = "\e[" NND = "#{ESC}0m" PASS = '=' FAIL = '*' ERROR = '!' PENDING = '路' attr_reader :title, :current, :example_results, :color_index def start(example_count) super(example_count) @current, @color_index = 0,0 @bar_length = 70 @example_results = [] end def example_passed(example) super(example) tick PASS end def example_pending(example) super(example) @pending_count =+1 tick PENDING end def example_failed(example) super(example) @failure_count =+1 tick FAIL end def start_dump @current = @example_count end def dump_summary(duration, example_count, failure_count, pending_count) dump_profile if profile_examples? && failure_count == 0 summary = "\nNyan Cat flew #{format_seconds(duration)} seconds".split(//).map { |c| rainbowify(c) } output.puts summary.join output.puts colorise_summary(summary_line(example_count, failure_count, pending_count)) dump_commands_to_rerun_failed_examples end def dump_failures # noop end # Increments the example count and displays the current progress # # Returns nothing def tick(mark = PASS) @example_results << mark @current = (@current > @example_count) ? @example_count : @current + 1 @title = " #{current}/#{example_count}" dump_progress end # Creates a rainbow trail # # Returns the sprintf format of the Nyan cat def nyan_trail width = percentage * @bar_length / 100 marker = @example_results.map{ |mark| highlight(mark) }.join sprintf("%s#{nyan_cat}%s", marker, " " * (@bar_length - width) ) end # Calculates the percentage completed any given point # # Returns Fixnum of the percentage def percentage @example_count.zero? ? 100 : @current * 100 / @example_count end # Ascii Nyan Cat. If tests are complete, Nyan Cat goes to sleep. If # there are failing or pending examples, Nyan Cat is concerned. # # Returns String Nyan Cat def nyan_cat if @failure_count > 0 || @pending_count > 0 '~|_(o.o)' elsif (@current == @example_count) '~|_(-.-)' else '~|_(^.^)' end end # Displays the current progress in all Nyan Cat glory # def dump_progress max_width = 80 line = sprintf("%-8s %s", @title[0,(7)] + ":", nyan_trail) tail = (@current == @example_count) ? "\n" : "\r" if line.length == max_width - 1 output.print line + tail output.flush elsif line.length >= max_width @bar_length = [@bar_length - (line.length - max_width + 1), 0].max @bar_length == 0 ? output.print( rainbowify(line + tail) ) : dump_progress else @bar_length += max_width - line.length + 1 dump_progress end end # Colorizes the string with raindow colors of the rainbow # def rainbowify(string) c = colors[@color_index % colors.size] @color_index += 1 "#{ESC}38;5;#{c}m#{string}#{NND}" end # Calculates the colors of the rainbow # def colors @colors ||= (0...(6 * 7)).map do |n| pi_3 = Math::PI / 3 n *= 1.0 / 6 r = (3 * Math.sin(n ) + 3).to_i g = (3 * Math.sin(n + 2 * pi_3) + 3).to_i b = (3 * Math.sin(n + 4 * pi_3) + 3).to_i 36 * r + 6 * g + b + 16 end end # Determines how to color the example. If pass, it is rainbowified, otherwise # we assign red if failed or yellow if an error occurred. # def highlight(mark = PASS) case mark when PASS; rainbowify mark when FAIL; red mark when ERROR; yellow mark else mark end end end
-
Finished in 3rd place with a final score of 2.1/5. (View the Gist)BackwardsFormatter.rbView full entry
require 'rspec/core/formatters/base_text_formatter' class BackwardsFormatter < RSpec::Core::Formatters::BaseTextFormatter def example_failed example super example dump_backwards_failures end def dump_failures; end def dump_summary *args; end def dump_backwards_failures 100.times { output.puts } output.puts summary_line(example_count, failed_examples.count, pending_examples.count) failed_examples.reverse.each_with_index do |example,i| dump_failure example, failed_examples.count - i - 1 dump_backtrace example end end end