minitest-holdify 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/holdify/feedback.rb +166 -0
- data/lib/holdify/hold.rb +22 -19
- data/lib/holdify/source.rb +5 -5
- data/lib/holdify/store.rb +58 -5
- data/lib/holdify.rb +11 -11
- data/lib/minitest/holdify_plugin.rb +33 -23
- metadata +2 -3
- data/lib/holdify/failure.rb +0 -110
- data/lib/holdify/ledger.rb +0 -82
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2fecbc870f5205d62da0200a806adc5dc5d47b99a81e53c1af4d50f3f8aaa21
|
|
4
|
+
data.tar.gz: 06aed82a631a3632c531ef311a53f89e339a00d122fbc75eb31c52f1723e8d24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebd7c7cbf15fc450a1a657affe961399ac916c3d57aafc74a89067638ba2d569c59ea134eecd811361d6970989a227dc6814bc1bec07673ee5ea2ca86ea5f877
|
|
7
|
+
data.tar.gz: c14c8219c1fbaaf1f10a6f28eef93f4696f71d59020090f85d96afe0ae431fa6a52db0a91d6f37f713ea266a6ef61369589bd85544a70c681f73c1902f0055eb
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
require 'open3'
|
|
6
|
+
|
|
7
|
+
module Holdify
|
|
8
|
+
# Feedback report on failure
|
|
9
|
+
class Feedback
|
|
10
|
+
attr_reader :xxhi_ref
|
|
11
|
+
|
|
12
|
+
def initialize(hold, hold_ref, *args)
|
|
13
|
+
test_lno = hold.test_loc.lineno
|
|
14
|
+
index = hold.session[test_lno].size - 1 # current index
|
|
15
|
+
xxh = hold.store.xxh(test_lno)
|
|
16
|
+
yaml_path = hold.store.path
|
|
17
|
+
|
|
18
|
+
@xxhi_ref = "<<< @xxh[i] --> #{xxh}[#{index}]"
|
|
19
|
+
|
|
20
|
+
@yaml_lno = find_yaml_lno(yaml_path, test_lno, xxh, index)
|
|
21
|
+
@yaml_ref = Holdify.relative("#{yaml_path}:#{@yaml_lno}")
|
|
22
|
+
|
|
23
|
+
@hold_ref = Holdify.relative(hold_ref)
|
|
24
|
+
test_ref = Holdify.relative(hold.test_loc.to_s.sub(/:in .*$/, ''))
|
|
25
|
+
@test_ref = test_ref unless @hold_ref == test_ref
|
|
26
|
+
|
|
27
|
+
@expected, @actual, @message = *args
|
|
28
|
+
|
|
29
|
+
# Extend with features
|
|
30
|
+
extend(Color) if Holdify.color
|
|
31
|
+
return unless Holdify.git
|
|
32
|
+
|
|
33
|
+
extend GitDiff
|
|
34
|
+
extend(Color::GitDiff) if Holdify.color
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def message = [@message, xxhi_ref, *file_refs, *diff, ''].join("\n")
|
|
38
|
+
|
|
39
|
+
def file_refs
|
|
40
|
+
["--- @stored --> #{@yaml_ref}", "+++ @tested --> #{@hold_ref}"].tap do |refs|
|
|
41
|
+
refs << " --> #{@test_ref}" if @test_ref
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def diff = ["- #{@expected.inspect}", "+ #{@actual.inspect}"]
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def find_yaml_lno(yaml_path, test_lno, xxh, index)
|
|
50
|
+
found = false
|
|
51
|
+
count = -1
|
|
52
|
+
File.foreach(yaml_path).with_index(1) do |line, ln|
|
|
53
|
+
if found
|
|
54
|
+
next unless line.start_with?('-')
|
|
55
|
+
|
|
56
|
+
count += 1
|
|
57
|
+
next unless count == index
|
|
58
|
+
|
|
59
|
+
return ln
|
|
60
|
+
else
|
|
61
|
+
found = line.match(/^L#{test_lno} #{xxh}:$/)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Methods enabling the git-diff feedback (no-color)
|
|
67
|
+
module GitDiff
|
|
68
|
+
def git_command = "git diff --no-index --no-color --unified=1000 #{@exp_path} #{@act_path}"
|
|
69
|
+
|
|
70
|
+
def diff
|
|
71
|
+
@exp_path = create_tempfile(@expected).path
|
|
72
|
+
@act_path = create_tempfile(@actual).path
|
|
73
|
+
|
|
74
|
+
stdout, = Open3.capture3(git_command)
|
|
75
|
+
regex = /\A[^@]*\r?\n/m # cleanup git headers
|
|
76
|
+
lines = stdout.sub(regex, '').split(/\r?\n/)
|
|
77
|
+
|
|
78
|
+
process_lines(lines)
|
|
79
|
+
ensure
|
|
80
|
+
# :nocov:
|
|
81
|
+
File.unlink(@exp_path) if @exp_path
|
|
82
|
+
File.unlink(@act_path) if @act_path
|
|
83
|
+
# :nocov:
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def create_tempfile(obj)
|
|
87
|
+
Tempfile.create.tap do |file|
|
|
88
|
+
file.write(YAML.dump(obj))
|
|
89
|
+
file.close
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def process_lines(lines)
|
|
94
|
+
width = 0
|
|
95
|
+
lineno = [@yaml_lno - 1]
|
|
96
|
+
lines.map.with_index do |line, i|
|
|
97
|
+
if i.zero? # @@ ... @@
|
|
98
|
+
w = line.scan(/,(\d+)/).flatten.map(&:to_i).max
|
|
99
|
+
width = (@yaml_lno + w).to_s.length
|
|
100
|
+
next line
|
|
101
|
+
end
|
|
102
|
+
next if i == 1 # ---
|
|
103
|
+
|
|
104
|
+
render_line(line, lineno, width)
|
|
105
|
+
end.compact
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def render_line(line, lineno, width)
|
|
109
|
+
type = line[0]
|
|
110
|
+
line = line[1..]
|
|
111
|
+
gutter = if type == '+'
|
|
112
|
+
' ' * width
|
|
113
|
+
else
|
|
114
|
+
lineno[0] += 1
|
|
115
|
+
lineno[0].to_s.rjust(width)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
"#{type}#{gutter} #{line}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Methods enabling ANSI feedback
|
|
123
|
+
module Color
|
|
124
|
+
SGR = { clear: "\e[0m",
|
|
125
|
+
red: "\e[31m",
|
|
126
|
+
green: "\e[32m",
|
|
127
|
+
yellow: "\e[33m",
|
|
128
|
+
magenta: "\e[35m" }.freeze
|
|
129
|
+
|
|
130
|
+
def wrap(color, string) = "#{SGR[color]}#{string}#{SGR[:clear]}"
|
|
131
|
+
|
|
132
|
+
def file_refs
|
|
133
|
+
refs = super
|
|
134
|
+
[wrap(:red, refs.shift), *refs.map { wrap(:green, _1) }]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def diff = [wrap(:red, "- #{@expected.inspect}"), wrap(:green, "+ #{@actual.inspect}")]
|
|
138
|
+
|
|
139
|
+
def xxhi_ref = wrap(:magenta, @xxhi_ref)
|
|
140
|
+
|
|
141
|
+
# Methods enabling the git-diff ANSI feedback
|
|
142
|
+
module GitDiff
|
|
143
|
+
def git_command = "git diff --no-index --color-words='[^ ]+|[ ]+' --unified=1000 #{@exp_path} #{@act_path}"
|
|
144
|
+
|
|
145
|
+
def render_line(line, lineno, width)
|
|
146
|
+
clean = line.gsub(/\e\[(1|22|0)m/, '').lstrip
|
|
147
|
+
added = clean.start_with?(SGR[:green]) && !line.include?(SGR[:red])
|
|
148
|
+
removed = clean.start_with?(SGR[:red]) && !line.include?(SGR[:green])
|
|
149
|
+
changed = line.include?(SGR[:red]) || line.include?(SGR[:green])
|
|
150
|
+
|
|
151
|
+
type, color = (added && ['+', :green]) || (removed && ['-', :red]) || (changed && ['~', :yellow])
|
|
152
|
+
|
|
153
|
+
sgr = SGR[color].to_s if color
|
|
154
|
+
gutter = if added
|
|
155
|
+
"#{sgr}#{type} #{' ' * width}#{SGR[:clear]}"
|
|
156
|
+
else
|
|
157
|
+
lineno[0] += 1
|
|
158
|
+
"#{sgr}#{type || ' '}#{lineno[0].to_s.rjust(width)}#{SGR[:clear]}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
"#{gutter} #{line}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
data/lib/holdify/hold.rb
CHANGED
|
@@ -1,57 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Holdify
|
|
4
|
-
# The *_hold
|
|
4
|
+
# The *_hold Assertion/Expectation
|
|
5
5
|
class Hold
|
|
6
|
-
attr_reader :forced, :store
|
|
6
|
+
attr_reader :forced, :store, :test_loc, :session
|
|
7
7
|
|
|
8
8
|
def initialize(test)
|
|
9
9
|
@test = test
|
|
10
10
|
@path, = test.method(test.name).source_location
|
|
11
11
|
@store = Holdify.stores(@path)
|
|
12
|
-
@session = Hash.new { |h, k| h[k] = [] } # {
|
|
12
|
+
@session = Hash.new { |h, k| h[k] = [] } # { lineno => [values] }
|
|
13
13
|
@forced = [] # [ lines ]
|
|
14
14
|
@added = [] # [ lines ]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def call(actual, force: false)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
raise "Could not find holdify statement at
|
|
18
|
+
@test_loc = find_test_loc
|
|
19
|
+
lineno = @test_loc.lineno
|
|
20
|
+
raise "Could not find holdify statement at lineno #{lineno}" unless @store.xxh(lineno)
|
|
21
21
|
|
|
22
|
-
@forced <<
|
|
22
|
+
@forced << lineno if force
|
|
23
23
|
|
|
24
|
-
values = @store.
|
|
25
|
-
index = @session[
|
|
24
|
+
values = @store.get_values(lineno)
|
|
25
|
+
index = @session[lineno].size
|
|
26
26
|
value = if force || Holdify.reconcile
|
|
27
27
|
actual
|
|
28
28
|
elsif values && index < values.size
|
|
29
29
|
values[index]
|
|
30
30
|
else
|
|
31
|
-
@added <<
|
|
31
|
+
@added << lineno
|
|
32
32
|
actual
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
@session[
|
|
35
|
+
@session[lineno] << value
|
|
36
36
|
value
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def save
|
|
40
|
-
@added.each { |
|
|
41
|
-
@session.each { |
|
|
40
|
+
@added.each { |lineno| warn "[holdify] Held new value for #{Holdify.relative(@path)}:#{lineno}" } unless Holdify.quiet
|
|
41
|
+
@session.each { |lineno, values| @store.set_values(lineno, values) }
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
#
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
# Find the location in the test that triggered the hold
|
|
48
|
-
def find_location
|
|
44
|
+
# Find the triggering LOC inside the test block/method
|
|
45
|
+
def find_test_loc
|
|
49
46
|
caller_locations(2).find do |location|
|
|
50
47
|
next unless location.path == @path
|
|
51
48
|
|
|
52
|
-
label
|
|
49
|
+
label = location.base_label
|
|
53
50
|
label == @test.name || label == '<top (required)>' || label == '<main>' || label.start_with?('<class:', '<module:')
|
|
54
51
|
end
|
|
55
52
|
end
|
|
53
|
+
|
|
54
|
+
def warn_for(actual)
|
|
55
|
+
warn("[holdify] The value from #{Holdify.relative(@test_loc.path)}:#{@test_loc.lineno} is:\n[holdify] => #{actual.inspect}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def feedback(*) = Feedback.new(self, *).message
|
|
56
59
|
end
|
|
57
60
|
end
|
data/lib/holdify/source.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Holdify
|
|
|
7
7
|
class Source
|
|
8
8
|
def initialize(path) = (@line_xxh, @xxh_lines = parse(path))
|
|
9
9
|
|
|
10
|
-
def xxh(
|
|
10
|
+
def xxh(lineno) = @line_xxh[lineno]
|
|
11
11
|
|
|
12
12
|
def lines(xxh) = @xxh_lines[xxh]
|
|
13
13
|
|
|
@@ -17,13 +17,13 @@ module Holdify
|
|
|
17
17
|
line_xxh = {}
|
|
18
18
|
xxh_lines = Hash.new { |h, k| h[k] = [] }
|
|
19
19
|
|
|
20
|
-
File.foreach(path).with_index(1) do |text,
|
|
20
|
+
File.foreach(path).with_index(1) do |text, lineno|
|
|
21
21
|
content = text.strip
|
|
22
22
|
next if content.empty?
|
|
23
23
|
|
|
24
|
-
xxh
|
|
25
|
-
line_xxh[
|
|
26
|
-
xxh_lines[xxh] <<
|
|
24
|
+
xxh = Digest::XXH3_64bits.hexdigest(content)
|
|
25
|
+
line_xxh[lineno] = xxh
|
|
26
|
+
xxh_lines[xxh] << lineno
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
[line_xxh, xxh_lines]
|
data/lib/holdify/store.rb
CHANGED
|
@@ -1,20 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'fileutils'
|
|
3
5
|
require 'forwardable'
|
|
4
|
-
require_relative 'source'
|
|
5
|
-
require_relative 'ledger'
|
|
6
6
|
|
|
7
7
|
module Holdify
|
|
8
8
|
# Interface to the Source (code) and the Ledger (data)
|
|
9
9
|
class Store
|
|
10
|
+
class NoAliasVisitor < Psych::Visitors::YAMLTree # :nodoc:
|
|
11
|
+
def register(target, obj); end
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
extend Forwardable
|
|
11
15
|
|
|
16
|
+
def_delegator :@source, :xxh
|
|
17
|
+
attr_reader :path
|
|
18
|
+
|
|
12
19
|
def initialize(path)
|
|
20
|
+
@path = "#{path}#{Holdify.store_ext}"
|
|
13
21
|
@source = Source.new(path)
|
|
14
|
-
@
|
|
22
|
+
FileUtils.rm_f(@path) if Holdify.reconcile
|
|
23
|
+
@data = File.exist?(@path) ? load_and_align : {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get_values(lineno) = @data[lineno]
|
|
27
|
+
|
|
28
|
+
def set_values(lineno, values) = (@data[lineno] = values)
|
|
29
|
+
|
|
30
|
+
def persist
|
|
31
|
+
return FileUtils.rm_f(@path) if @data.empty?
|
|
32
|
+
|
|
33
|
+
output = {}
|
|
34
|
+
@data.keys.sort.each do |lineno|
|
|
35
|
+
xxh = @source.xxh(lineno)
|
|
36
|
+
next unless xxh
|
|
37
|
+
|
|
38
|
+
output["L#{lineno} #{xxh}"] = @data[lineno]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
File.write(@path, hold_dump(output))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def hold_dump(obj)
|
|
47
|
+
visitor = NoAliasVisitor.create
|
|
48
|
+
visitor << obj
|
|
49
|
+
visitor.tree.to_yaml
|
|
15
50
|
end
|
|
16
51
|
|
|
17
|
-
|
|
18
|
-
|
|
52
|
+
def load_and_align
|
|
53
|
+
{}.tap do |aligned|
|
|
54
|
+
data = YAML.unsafe_load_file(@path) || {}
|
|
55
|
+
data.group_by { |k, _| k.split.last }.each do |xxh, entries|
|
|
56
|
+
lines = @source.lines(xxh)
|
|
57
|
+
next if lines.empty?
|
|
58
|
+
|
|
59
|
+
# Position of the held lines compared to the source lines
|
|
60
|
+
stayed, moved = entries.map { |key, values| { line: key[/\d+/].to_i, values: } }
|
|
61
|
+
.partition { lines.include?(_1[:line]) }
|
|
62
|
+
moved.sort_by! { _1[:line] }
|
|
63
|
+
|
|
64
|
+
# Align lines
|
|
65
|
+
lines.each do |lineno|
|
|
66
|
+
match = stayed.find { _1[:line] == lineno } || moved.shift
|
|
67
|
+
aligned[lineno] = match[:values] if match
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
19
72
|
end
|
|
20
73
|
end
|
data/lib/holdify.rb
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'holdify/store'
|
|
4
3
|
require_relative 'holdify/hold'
|
|
4
|
+
require_relative 'holdify/feedback'
|
|
5
|
+
require_relative 'holdify/source'
|
|
6
|
+
require_relative 'holdify/store'
|
|
5
7
|
|
|
6
8
|
# The container module
|
|
7
9
|
module Holdify
|
|
8
|
-
VERSION = '1.
|
|
9
|
-
CONFIG = { ext: '.yaml' }.freeze
|
|
10
|
+
VERSION = '1.3.0'
|
|
10
11
|
|
|
11
12
|
class << self
|
|
12
|
-
attr_accessor :reconcile, :quiet
|
|
13
|
+
attr_accessor :reconcile, :quiet, :git, :pwd, :color, :rel_paths, :store_ext
|
|
14
|
+
|
|
15
|
+
def persist_all! = @stores&.each_value(&:persist)
|
|
13
16
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
def relative(path)
|
|
18
|
+
return path unless rel_paths
|
|
19
|
+
|
|
20
|
+
path.sub(%r{^#{pwd}/}, '')
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def stores(path = nil)
|
|
@@ -23,10 +27,6 @@ module Holdify
|
|
|
23
27
|
@stores[path] ||= Store.new(path)
|
|
24
28
|
end
|
|
25
29
|
end
|
|
26
|
-
|
|
27
|
-
def persist_all!
|
|
28
|
-
@stores&.each_value(&:persist)
|
|
29
|
-
end
|
|
30
30
|
end
|
|
31
31
|
@mutex = Mutex.new
|
|
32
32
|
@stores = {}
|
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'holdify'
|
|
4
|
-
require 'holdify/failure'
|
|
5
4
|
|
|
6
5
|
# Implement the minitest plugin
|
|
7
6
|
module Minitest
|
|
8
|
-
# Register the after_run hook to persist all data
|
|
9
|
-
def self.plugin_holdify_init(_options)
|
|
10
|
-
Minitest.after_run { Holdify.persist_all! }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
7
|
# Set the Holdify options
|
|
14
|
-
def self.plugin_holdify_options(opts,
|
|
15
|
-
opts.on '--holdify-reconcile', 'Reconcile the held values with the new ones' do
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
def self.plugin_holdify_options(opts, options)
|
|
9
|
+
opts.on '--holdify-reconcile', 'Reconcile the held values with the new ones (enables quiet)' do
|
|
10
|
+
options[:holdify_reconcile] = true
|
|
11
|
+
options[:holdify_quiet] = true
|
|
18
12
|
end
|
|
19
|
-
|
|
20
13
|
opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
|
|
21
|
-
|
|
14
|
+
options[:holdify_quiet] = true
|
|
15
|
+
end
|
|
16
|
+
opts.on '--holdify-no-git-diff', 'Disable git-diff' do
|
|
17
|
+
options[:holdify_no_git_diff] = true
|
|
18
|
+
end
|
|
19
|
+
opts.on '--holdify-no-color', 'Disable colored output' do
|
|
20
|
+
options[:holdify_no_color] = true
|
|
21
|
+
end
|
|
22
|
+
opts.on '--holdify-no-rel-paths', 'Disable relative paths in file references' do
|
|
23
|
+
options[:holdify_no_rel_paths] = true
|
|
22
24
|
end
|
|
25
|
+
opts.on '--holdify-store-ext EXT', 'The yaml store extension (default .yaml)' do |ext|
|
|
26
|
+
options[:holdify_store_ext] = ext
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Register the after_run hook to persist all data
|
|
31
|
+
def self.plugin_holdify_init(options)
|
|
32
|
+
Holdify.reconcile = options[:holdify_reconcile]
|
|
33
|
+
Holdify.quiet = options[:holdify_quiet]
|
|
34
|
+
Holdify.git = system('git --version', out: File::NULL, err: File::NULL) unless options[:holdify_no_git_diff]
|
|
35
|
+
Holdify.pwd = Holdify.git ? `git rev-parse --show-toplevel`.strip : Dir.pwd
|
|
36
|
+
Holdify.color = !ENV.key?('NO_COLOR') unless options[:holdify_no_color]
|
|
37
|
+
Holdify.rel_paths = true unless options[:holdify_no_rel_paths]
|
|
38
|
+
Holdify.store_ext = options[:holdify_store_ext] || '.yaml'
|
|
39
|
+
|
|
40
|
+
Minitest.after_run { Holdify.persist_all! }
|
|
23
41
|
end
|
|
24
42
|
|
|
25
43
|
# Reopen the minitest class
|
|
@@ -34,7 +52,7 @@ module Minitest
|
|
|
34
52
|
|
|
35
53
|
path, = method(name).source_location
|
|
36
54
|
msg = +%([holdify] the value has been stored: remove the "!" suffix to pass the test\n)
|
|
37
|
-
msg << @hold.forced.uniq.map { |
|
|
55
|
+
msg << @hold.forced.uniq.map { |lineno| " #{path}:#{lineno}" }.join("\n")
|
|
38
56
|
|
|
39
57
|
raise Minitest::Assertion, msg
|
|
40
58
|
end
|
|
@@ -56,18 +74,10 @@ module Minitest
|
|
|
56
74
|
send(assertion || :assert_equal, expected, actual, message)
|
|
57
75
|
end
|
|
58
76
|
rescue Minitest::Assertion => e
|
|
59
|
-
|
|
60
|
-
metadata = @hold.store.lookup(location.lineno, @hold.current_index(location.lineno))
|
|
61
|
-
hold_msg = Holdify::Failure.new(expected, actual, e.message, metadata:, location:).message
|
|
62
|
-
|
|
63
|
-
msg = message ? "#{message}\n#{hold_msg}" : hold_msg
|
|
64
|
-
raise Minitest::Assertion, msg
|
|
77
|
+
raise Minitest::Assertion, @hold.feedback(e.location, expected, actual, message)
|
|
65
78
|
end
|
|
66
79
|
|
|
67
|
-
if inspect
|
|
68
|
-
location = @hold.find_location
|
|
69
|
-
warn "[holdify] The value from #{location.path}:#{location.lineno} is:\n[holdify] => #{actual.inspect}"
|
|
70
|
-
end
|
|
80
|
+
@hold.warn_for(actual) if inspect
|
|
71
81
|
|
|
72
82
|
expected
|
|
73
83
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: minitest-holdify
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Domizio Demichelis
|
|
@@ -47,9 +47,8 @@ extra_rdoc_files: []
|
|
|
47
47
|
files:
|
|
48
48
|
- LICENSE.txt
|
|
49
49
|
- lib/holdify.rb
|
|
50
|
-
- lib/holdify/
|
|
50
|
+
- lib/holdify/feedback.rb
|
|
51
51
|
- lib/holdify/hold.rb
|
|
52
|
-
- lib/holdify/ledger.rb
|
|
53
52
|
- lib/holdify/source.rb
|
|
54
53
|
- lib/holdify/store.rb
|
|
55
54
|
- lib/minitest-holdify.rb
|
data/lib/holdify/failure.rb
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'yaml'
|
|
4
|
-
require 'tempfile'
|
|
5
|
-
require 'open3'
|
|
6
|
-
|
|
7
|
-
module Holdify
|
|
8
|
-
# Diff print failure report
|
|
9
|
-
class Failure
|
|
10
|
-
GUTTER_WIDTH = 3
|
|
11
|
-
|
|
12
|
-
def initialize(expected, actual, message, location:, metadata:)
|
|
13
|
-
@expected = expected
|
|
14
|
-
@actual = actual
|
|
15
|
-
@message = message
|
|
16
|
-
@location = location
|
|
17
|
-
@metadata = metadata
|
|
18
|
-
@config = Holdify.config
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def message
|
|
22
|
-
output = add_headers
|
|
23
|
-
@config[:git] ? output.push(*add_diff) : output.push(@message)
|
|
24
|
-
output.join("\n")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def add_headers
|
|
28
|
-
["#{ansi[:red]}--- @yaml#{ansi[:reset]} >>> #{@metadata[:path]}:#{@metadata[:line]}",
|
|
29
|
-
"#{ansi[:green]}+++ @test#{ansi[:reset]} >>> #{@location.path}:#{@location.lineno}",
|
|
30
|
-
"#{ansi[:inverse]}<-- #{@metadata[:key]} #{ansi[:reset]}"]
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def ansi
|
|
34
|
-
@config[:color] ? { inverse: "\e[7m", reset: "\e[0m", red: "\e[31m", green: "\e[32m" } : {}
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def git_command
|
|
38
|
-
if @config[:color]
|
|
39
|
-
regex = '[^[:space:]]+|[[:space:]]+'
|
|
40
|
-
%W[git diff --no-index --color-words=#{regex} --unified=1000 #{@exp_file.path} #{@act_file.path}]
|
|
41
|
-
else
|
|
42
|
-
%W[git diff --no-index --no-color --unified=1000 #{@exp_file.path} #{@act_file.path}]
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def add_diff
|
|
47
|
-
@exp_file = create_tempfile(@expected)
|
|
48
|
-
@act_file = create_tempfile(@actual)
|
|
49
|
-
@config[:color] ? color_process_lines : process_lines
|
|
50
|
-
rescue Errno::ENOENT
|
|
51
|
-
[]
|
|
52
|
-
ensure
|
|
53
|
-
@exp_file&.close!
|
|
54
|
-
@act_file&.close!
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def diff_lines
|
|
58
|
-
stdout, = Open3.capture3(*git_command)
|
|
59
|
-
return [] if stdout.to_s.empty?
|
|
60
|
-
|
|
61
|
-
# Match everything from the start up to the LAST @@...--- sequence
|
|
62
|
-
# .* is greedy, so it skips all earlier hunks
|
|
63
|
-
regex = /\A.*@@.*?\r?\n[[:blank:]]*---(\e\[[0-9;]*m)?\r?\n/m
|
|
64
|
-
body = stdout.sub(regex, '')
|
|
65
|
-
|
|
66
|
-
body.split(/\r?\n/)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def color_process_lines
|
|
70
|
-
line_num = @metadata[:line]
|
|
71
|
-
diff_lines.map do |line|
|
|
72
|
-
clean = line.gsub(/\e\[(1|22|0)m/, '').lstrip
|
|
73
|
-
added = clean.start_with?(ansi[:green]) && !line.include?(ansi[:red])
|
|
74
|
-
|
|
75
|
-
if added
|
|
76
|
-
gutter = ' ' * GUTTER_WIDTH
|
|
77
|
-
else
|
|
78
|
-
gutter = line_num.to_s.rjust(GUTTER_WIDTH)
|
|
79
|
-
line_num += 1
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
"#{ansi[:inverse]}#{gutter}#{ansi[:reset]} #{line}"
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def process_lines
|
|
87
|
-
line_num = @metadata[:line]
|
|
88
|
-
diff_lines.map do |line|
|
|
89
|
-
type = line[0]
|
|
90
|
-
line = line[1..]
|
|
91
|
-
|
|
92
|
-
if type == '+'
|
|
93
|
-
gutter = ' ' * GUTTER_WIDTH
|
|
94
|
-
else
|
|
95
|
-
gutter = line_num.to_s.rjust(GUTTER_WIDTH)
|
|
96
|
-
line_num += 1
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
"#{gutter} #{type} #{line}"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def create_tempfile(obj)
|
|
104
|
-
Tempfile.new(%w[holdify .yaml]).tap do |file|
|
|
105
|
-
file.write(YAML.dump(obj))
|
|
106
|
-
file.flush
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
data/lib/holdify/ledger.rb
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'yaml'
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
module Holdify
|
|
7
|
-
# Manages the persistence and alignment of stored values
|
|
8
|
-
class Ledger
|
|
9
|
-
def initialize(path, source)
|
|
10
|
-
@path = "#{path}#{CONFIG[:ext]}"
|
|
11
|
-
@source = source
|
|
12
|
-
FileUtils.rm_f(@path) if Holdify.reconcile
|
|
13
|
-
@data = File.exist?(@path) ? load_and_align : {}
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def get(line) = @data[line]
|
|
17
|
-
|
|
18
|
-
def set(line, values) = (@data[line] = values)
|
|
19
|
-
|
|
20
|
-
def persist
|
|
21
|
-
return FileUtils.rm_f(@path) if @data.empty?
|
|
22
|
-
|
|
23
|
-
output = {}
|
|
24
|
-
@data.keys.sort.each do |line|
|
|
25
|
-
xxh = @source.xxh(line)
|
|
26
|
-
next unless xxh
|
|
27
|
-
|
|
28
|
-
output["L#{line} #{xxh}"] = @data[line]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
content = YAML.dump(output)
|
|
32
|
-
File.write(@path, content)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Used in Failure to get the actual storage metadata
|
|
36
|
-
def lookup(lineno, index = 0)
|
|
37
|
-
return unless File.exist?(@path)
|
|
38
|
-
|
|
39
|
-
xxh = @source.xxh(lineno)
|
|
40
|
-
key = "L#{lineno} #{xxh}"
|
|
41
|
-
|
|
42
|
-
found = false
|
|
43
|
-
count = -1
|
|
44
|
-
|
|
45
|
-
File.foreach(@path).with_index(1) do |content, ln|
|
|
46
|
-
if found
|
|
47
|
-
next unless content.start_with?('-')
|
|
48
|
-
|
|
49
|
-
count += 1
|
|
50
|
-
next unless count == index
|
|
51
|
-
|
|
52
|
-
return { path: @path, line: ln, key: key }
|
|
53
|
-
else
|
|
54
|
-
found = content.match(/\b#{xxh}\b/)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
def load_and_align
|
|
62
|
-
{}.tap do |aligned|
|
|
63
|
-
data = YAML.unsafe_load_file(@path) || {}
|
|
64
|
-
data.group_by { |k, _| k.split.last }.each do |xxh, entries|
|
|
65
|
-
lines = @source.lines(xxh)
|
|
66
|
-
next if lines.empty?
|
|
67
|
-
|
|
68
|
-
# Position of the held lines compared to the source lines
|
|
69
|
-
stayed, moved = entries.map { |key, values| { line: key[/\d+/].to_i, values: } }
|
|
70
|
-
.partition { lines.include?(_1[:line]) }
|
|
71
|
-
moved.sort_by! { _1[:line] }
|
|
72
|
-
|
|
73
|
-
# Align lines
|
|
74
|
-
lines.each do |line|
|
|
75
|
-
match = stayed.find { _1[:line] == line } || moved.shift
|
|
76
|
-
aligned[line] = match[:values] if match
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|