Class: SixthSense::Guardrail::Baseline

Inherits:
Object
  • Object
show all
Defined in:
lib/sixth_sense/guardrail/baseline.rb

Constant Summary collapse

DEFAULT_PATH =
".sixth_sense_baseline.json"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:, data:) ⇒ Baseline

Returns a new instance of Baseline.



28
29
30
31
# File 'lib/sixth_sense/guardrail/baseline.rb', line 28

def initialize(path:, data:)
  @path = path
  @data = data
end

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



12
13
14
# File 'lib/sixth_sense/guardrail/baseline.rb', line 12

def data
  @data
end

#pathObject (readonly)

Returns the value of attribute path.



12
13
14
# File 'lib/sixth_sense/guardrail/baseline.rb', line 12

def path
  @path
end

Class Method Details

.empty_dataObject



19
20
21
22
23
24
25
26
# File 'lib/sixth_sense/guardrail/baseline.rb', line 19

def self.empty_data
  {
    "version" => 1,
    "generated_at" => nil,
    "level" => 0,
    "files" => {}
  }
end

.load(path = DEFAULT_PATH) ⇒ Object



14
15
16
17
# File 'lib/sixth_sense/guardrail/baseline.rb', line 14

def self.load(path = DEFAULT_PATH)
  data = File.file?(path) ? JSON.parse(File.read(path)) : empty_data
  new(path: path, data: data)
end

Instance Method Details

#digest_for(test_file) ⇒ Object



103
104
105
106
107
108
109
110
111
112
# File 'lib/sixth_sense/guardrail/baseline.rb', line 103

def digest_for(test_file)
  digest = Digest::SHA256.new
  ([test_file.path] + test_file.sut_candidates.map(&:path)).uniq.each do |path|
    next unless File.file?(path)

    digest.update(path)
    digest.update(File.binread(path))
  end
  "sha256:#{digest.hexdigest}"
end

#update(reports, level:, force: false) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/sixth_sense/guardrail/baseline.rb', line 86

def update(reports, level:, force: false)
  return self if !force && data.fetch("level", 0).to_i > level

  next_data = self.class.empty_data
  next_data["generated_at"] = Time.now.utc.iso8601
  next_data["level"] = level
  reports.each do |report|
    existing = data.fetch("files", {})[report.test_file.path] || {}
    next_data["files"][report.test_file.path] = updated_entry(report, existing, force: force)
  end
  @data = next_data
end

#violations(reports, current_level:, tolerance:) ⇒ Object



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
# File 'lib/sixth_sense/guardrail/baseline.rb', line 33

def violations(reports, current_level:, tolerance:)
  return [] if current_level < data.fetch("level", 0).to_i

  reports.flat_map do |report|
    baseline_file = data.fetch("files", {})[report.test_file.path]
    next [] unless baseline_file
    next [] if baseline_file["digest"] == digest_for(report.test_file)

    report.axis_scores.filter_map do |score|
      next unless score.measured?

      baseline_value = baseline_file[score.axis.to_s]
      next unless baseline_value
      next unless score.value < baseline_value.to_f - tolerance

      {
        path: report.test_file.path,
        axis: score.axis,
        current: score.value,
        baseline: baseline_value.to_f,
        tolerance: tolerance
      }
    end
  end
end

#warnings(reports, current_level:, tolerance:) ⇒ Object



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
# File 'lib/sixth_sense/guardrail/baseline.rb', line 59

def warnings(reports, current_level:, tolerance:)
  return [] if current_level < data.fetch("level", 0).to_i

  reports.flat_map do |report|
    baseline_file = data.fetch("files", {})[report.test_file.path]
    next [] unless baseline_file
    next [] unless baseline_file["digest"] == digest_for(report.test_file)

    report.axis_scores.filter_map do |score|
      next unless score.measured?

      baseline_value = baseline_file[score.axis.to_s]
      next unless baseline_value
      next unless (score.value - baseline_value.to_f).abs > tolerance

      {
        type: :flaky_measurement,
        path: report.test_file.path,
        axis: score.axis,
        current: score.value,
        baseline: baseline_value.to_f,
        tolerance: tolerance
      }
    end
  end
end

#writeObject



99
100
101
# File 'lib/sixth_sense/guardrail/baseline.rb', line 99

def write
  File.write(path, JSON.pretty_generate(data) + "\n")
end