Class: RSpec::Signal::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec/signal/message.rb

Overview

The human-readable failure message, plus a normalized form used for grouping.

Constant Summary collapse

ANSI =
/\e\[[0-9;]*[A-Za-z]/.freeze
NORMALIZERS =

Volatile substrings that would otherwise split identical failures into separate signatures.

[
  [/0x[0-9a-f]{4,}/i, "0xXXXX"],
  [/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i, "<uuid>"],
  [/\b\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?/, "<time>"],
  # No `\b` here: there is no word boundary between a space and a slash.
  [%r{(?<![\w/])(?:/tmp|/var/folders|/private/var/folders)/[\w./+-]+}, "<tmppath>"],
  [/\bid[:=]\s*\d+/i,                                             "id=<n>"],
  [/\b\d{4,}\b/,                                                  "<n>"],
  [/:\d+:in\s+[`'][^'`]*['`]/,                                    ""],
  # Diff hunk headers count lines; the lines themselves are already in
  # the message, and the counts split otherwise identical failures.
  [/@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/,                          "@@"],
  # An HTML summary's size differs between two renderings of the same
  # broken page; its title and headings do not, and those are the part
  # worth fingerprinting.
  [/\[HTML document:[^\]]*\]/,                                    "[HTML document]"]
].freeze
MAX_FINGERPRINT_CHARS =
400
DEFAULT_HTML_THRESHOLD =

Smallest HTML blob worth replacing with a summary. Below this the markup is short enough to read, and reading it is the point.

1_500

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(lines, redactor:, project:, html_threshold: DEFAULT_HTML_THRESHOLD, cause_lines: []) ⇒ Message

Returns a new instance of Message.

Parameters:

  • lines (Array<String>)

    message lines as RSpec presents them

  • redactor (Redactor)
  • project (Project)
  • html_threshold (Integer, nil) (defaults to: DEFAULT_HTML_THRESHOLD)

    nil disables HTML reduction

  • cause_lines (Array<String>) (defaults to: [])

    the Caused by ... chain, already bounded by FailureBuilder -- kept apart from lines so neither the line budget in #body nor the character budget in #normalized can push it out. It is very often the actual answer.



46
47
48
49
50
51
# File 'lib/rspec/signal/message.rb', line 46

def initialize(lines, redactor:, project:, html_threshold: DEFAULT_HTML_THRESHOLD, cause_lines: [])
  @redactor = redactor
  @project = project
  @lines = trim(squeeze(reduce_html(normalize(lines), html_threshold)))
  @cause_lines = trim(squeeze(reduce_html(normalize(cause_lines), html_threshold)))
end

Instance Attribute Details

#cause_linesObject (readonly)

Returns the value of attribute cause_lines.



36
37
38
# File 'lib/rspec/signal/message.rb', line 36

def cause_lines
  @cause_lines
end

#linesObject (readonly)

Returns the value of attribute lines.



36
37
38
# File 'lib/rspec/signal/message.rb', line 36

def lines
  @lines
end

Instance Method Details

#body(max_lines: 30, max_diff_lines: 20) ⇒ Object

The message body for the report, with oversized diffs trimmed.

Diffs are the single biggest source of bloat in RSpec output, and the first lines of a diff almost always carry the signal. The cause chain is appended after truncation, never counted against max_lines: a verbose wrapper message must not be able to push the root cause out.



76
77
78
79
80
81
# File 'lib/rspec/signal/message.rb', line 76

def body(max_lines: 30, max_diff_lines: 20)
  kept = truncated_lines(max_lines: max_lines, max_diff_lines: max_diff_lines)
  return kept if @cause_lines.empty?

  kept + ["", *@cause_lines]
end

#empty?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/rspec/signal/message.rb', line 53

def empty?
  @lines.all?(&:empty?) && @cause_lines.all?(&:empty?)
end

#headline(limit = 160) ⇒ Object

First meaningful line, for headings and one-line summaries.



64
65
66
67
68
# File 'lib/rspec/signal/message.rb', line 64

def headline(limit = 160)
  line = @lines.find { |l| !l.strip.empty? }.to_s.strip
  line = @lines.reject(&:empty?)[1].to_s.strip if line.empty?
  truncate(line, limit)
end

#normalizedObject

Stable form used for fingerprinting. The cause chain is appended after the main text is truncated to MAX_FINGERPRINT_CHARS, so two otherwise-identical wrapper messages with different causes never collapse into one signature just because the wrapper is long.



87
88
89
90
91
92
93
# File 'lib/rspec/signal/message.rb', line 87

def normalized
  @normalized ||= begin
    main = truncate(normalize_for_fingerprint(@lines.join(" ")), MAX_FINGERPRINT_CHARS)
    cause = normalize_for_fingerprint(@cause_lines.join(" "))
    cause.empty? ? main : "#{main} #{cause}"
  end
end

#textObject



57
58
59
60
61
# File 'lib/rspec/signal/message.rb', line 57

def text
  return @lines.join("\n") if @cause_lines.empty?

  "#{@lines.join("\n")}\n\n#{@cause_lines.join("\n")}"
end