Class: RSpec::FlakeClassifier::Deflaker

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec/flake/classifier/deflaker.rb,
sig/rspec/flake/classifier.rbs

Constant Summary collapse

Result =

Returns:

  • (singleton(::Struct))

Instance Method Summary collapse

Instance Method Details

#git_changed_lines(base = "HEAD") ⇒ Hash[String, Array[Integer]]

Parameters:

  • base (String) (defaults to: "HEAD")

Returns:

  • (Hash[String, Array[Integer]])


36
37
38
39
40
41
# File 'lib/rspec/flake/classifier/deflaker.rb', line 36

def git_changed_lines(base = "HEAD")
  stdout, _stderr, status = Open3.capture3("git", "diff", "--unified=0", base)
  return {} unless status.success?

  parse_diff(stdout)
end

#parse_diff(diff) ⇒ Hash[String, Array[Integer]]

Parameters:

  • diff (String)

Returns:

  • (Hash[String, Array[Integer]])


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rspec/flake/classifier/deflaker.rb', line 43

def parse_diff(diff)
  changed = Hash.new { |hash, key| hash[key] = [] }
  current_file = nil

  diff.each_line do |line|
    if (match = line.match(%r{\A\+\+\+ b/(.+)}))
      current_file = match[1]
      next
    end

    next unless current_file

    match = line.match(/\A@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/)
    next unless match

    start = match[1].to_i
    length = (match[2] || "1").to_i
    next if length.zero?

    changed[current_file].concat((start...(start + length)).to_a)
  end

  changed.transform_values { |lines| lines.uniq.sort }
end

#suspect?(coverage:, changed_lines: nil, base: "HEAD") ⇒ Boolean

Parameters:

  • coverage: (Object)
  • changed_lines: (Object) (defaults to: nil)
  • base: (String) (defaults to: "HEAD")

Returns:

  • (Boolean)


23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/rspec/flake/classifier/deflaker.rb', line 23

def suspect?(coverage:, changed_lines: nil, base: "HEAD")
  covered = normalize_lines(coverage)
  changed = normalize_lines(changed_lines || git_changed_lines(base))
  intersection = intersect(covered, changed)

  Result.new(
    suspected: !covered.empty? && intersection.empty?,
    reason: intersection.empty? ? "covered code did not intersect changed lines" : "covered changed lines",
    covered_lines: covered,
    changed_lines: changed
  )
end