Class: RspecErrorSummary::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec_error_summary.rb

Overview

!/usr/bin/env ruby

Class Method Summary collapse

Class Method Details

.parse_spec_report(options = {}) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/rspec_error_summary.rb', line 9

def self.parse_spec_report(options = {})
  config = RSpec.configuration

  formatter = RSpec::Core::Formatters::JsonFormatter.new(config.output_stream)

  # create reporter with json formatter
  reporter =  RSpec::Core::Reporter.new(config)
  config.instance_variable_set(:@reporter, reporter)

  # internal hack
  # api may not be stable, works on Rspec version 3.1
  loader = config.send(:formatter_loader)
  notifications = loader.send(:notifications_for, RSpec::Core::Formatters::JsonFormatter)

  reporter.register_listener(formatter, *notifications)

  path = options[:path] || "./spec/"
  desired_failure_string = options[:search] || ""
  be_verbose = options[:verbose] || false

  puts "Running tests... This may take a few minutes..."

  RSpec::Core::Runner.run([path])

  json = formatter.output_hash


  failed_tests = json[:examples].select { |test| test[:status] == "failed" && test[:exception][:message].include?(desired_failure_string) }

  failure_messages = failed_tests.map do |test| 
    # Consume the failed tests and provide a more simplified hash with just the required info

    # Strip out the line breaks and replace standard space indents with tabs
    msg = test[:exception][:message].gsub("\n", " ").gsub("     ", "\n\t\t  ").strip
    test_split = msg.split("#<")
    
    if(test_split.count > 1 && test_split[0] != "" && !options[:verbose])
      # Indicate that there is more to the message to be seen using the verbose flag
      # or by running rspec manually on the affected files
      msg = test_split[0] + "..."
    end

    { 
      message: msg,
      file_path: test[:file_path],
      line_number: test[:line_number]
    }
    
  end

  uniq_failure_messages = failure_messages.uniq { |test| test[:message] }
  uniq_failure_messages_for_output = []
  uniq_failure_messages.map do |failed_test|
    matching_failures = failure_messages.select { |test| test[:message] == failed_test[:message] }
    files = matching_failures.map{ |test| [test[:file_path],test[:line_number]]}
    files.sort! do |a,b|
      # Sort by file path first, then line number
      comp = (a[0] <=> b[0])
      comp.zero? ? (a[1] <=> b[1]) : comp
    end

    uniq_failure_messages_for_output << { 
                                          message: failed_test[:message],
                                          count: matching_failures.count,
                                          files: files
                                        }

  end

  # Sort by number of occurrences in descending order
  uniq_failure_messages_for_output.sort!{ |a,b|  b[:count].to_i <=> a[:count].to_i }

  # Empty space between any normal RSpec output and our custom text
  puts "\n\n"
  puts "Failure message report:\n\n"
  puts "Total # of failures: #{ failed_tests.count.to_s.colorize(:red) }"
  puts "Total # of unique errors: #{ uniq_failure_messages_for_output.count.to_s.colorize(:red) }\n\n"

  if uniq_failure_messages_for_output.count == 0
    puts ("Either no failures in the specified tests, or the desired error you entered could not be found in the results").colorize(:green)
  else
    uniq_failure_messages_for_output.each do |failure|
      puts " "
      count_text = ("#{failure[:count]} #{ failure[:count] > 1 ? 'occurrences' : 'occurrence'}").colorize(:red)
      msg_text = ((be_verbose ? failure[:message] : failure[:message].truncate(140,separator: /\s/))).colorize(:brown)
      puts %Q~#{count_text} - #{msg_text}~
      failure[:files].each do |file|
        file_text = (file[0]).colorize(:blue)
        line_text = ("#{file[1].to_s}").colorize(:green)
        puts "\t\t#{file_text}:#{line_text}"
      end
    end
  end
  puts "\n\n"
 
end