minitest-holdify 1.1.3 → 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 +25 -19
- data/lib/holdify/source.rb +5 -5
- data/lib/holdify/store.rb +58 -5
- data/lib/holdify.rb +13 -16
- data/lib/minitest/holdify_plugin.rb +36 -32
- metadata +2 -3
- data/lib/holdify/ledger.rb +0 -58
- data/lib/holdify/pretty.rb +0 -90
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,54 +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
|
|
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] = [] } # {
|
|
13
|
-
@forced = [] # [
|
|
14
|
-
@added = [] # [
|
|
12
|
+
@session = Hash.new { |h, k| h[k] = [] } # { lineno => [values] }
|
|
13
|
+
@forced = [] # [ lines ]
|
|
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
|
-
# Find the
|
|
45
|
-
def
|
|
46
|
-
caller_locations.find do |location|
|
|
44
|
+
# Find the triggering LOC inside the test block/method
|
|
45
|
+
def find_test_loc
|
|
46
|
+
caller_locations(2).find do |location|
|
|
47
47
|
next unless location.path == @path
|
|
48
48
|
|
|
49
|
-
label
|
|
49
|
+
label = location.base_label
|
|
50
50
|
label == @test.name || label == '<top (required)>' || label == '<main>' || label.start_with?('<class:', '<module:')
|
|
51
51
|
end
|
|
52
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
|
|
53
59
|
end
|
|
54
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,16 +1,24 @@
|
|
|
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
|
-
|
|
13
|
+
attr_accessor :reconcile, :quiet, :git, :pwd, :color, :rel_paths, :store_ext
|
|
14
|
+
|
|
15
|
+
def persist_all! = @stores&.each_value(&:persist)
|
|
16
|
+
|
|
17
|
+
def relative(path)
|
|
18
|
+
return path unless rel_paths
|
|
19
|
+
|
|
20
|
+
path.sub(%r{^#{pwd}/}, '')
|
|
21
|
+
end
|
|
14
22
|
|
|
15
23
|
def stores(path = nil)
|
|
16
24
|
return @stores unless path
|
|
@@ -19,17 +27,6 @@ module Holdify
|
|
|
19
27
|
@stores[path] ||= Store.new(path)
|
|
20
28
|
end
|
|
21
29
|
end
|
|
22
|
-
|
|
23
|
-
def persist_all!
|
|
24
|
-
@stores&.each_value(&:persist)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def pretty
|
|
28
|
-
return @pretty unless @pretty.nil?
|
|
29
|
-
|
|
30
|
-
@pretty = $stdout.tty? && !ENV.key?('NO_COLOR') && ENV['TERM'] != 'dumb' &&
|
|
31
|
-
system('git --version', out: File::NULL, err: File::NULL)
|
|
32
|
-
end
|
|
33
30
|
end
|
|
34
31
|
@mutex = Mutex.new
|
|
35
32
|
@stores = {}
|
|
@@ -1,29 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'holdify'
|
|
4
|
-
require 'holdify/pretty'
|
|
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
|
-
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
opts.on '--holdify-pretty', 'Format the stored values with pretty print' do
|
|
21
|
-
Holdify.pretty = true
|
|
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
|
|
22
12
|
end
|
|
23
|
-
|
|
24
13
|
opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
|
|
25
|
-
|
|
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
|
|
26
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! }
|
|
27
41
|
end
|
|
28
42
|
|
|
29
43
|
# Reopen the minitest class
|
|
@@ -37,10 +51,9 @@ module Minitest
|
|
|
37
51
|
return unless @hold.forced.any?
|
|
38
52
|
|
|
39
53
|
path, = method(name).source_location
|
|
40
|
-
msg
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
MSG
|
|
54
|
+
msg = +%([holdify] the value has been stored: remove the "!" suffix to pass the test\n)
|
|
55
|
+
msg << @hold.forced.uniq.map { |lineno| " #{path}:#{lineno}" }.join("\n")
|
|
56
|
+
|
|
44
57
|
raise Minitest::Assertion, msg
|
|
45
58
|
end
|
|
46
59
|
end
|
|
@@ -60,20 +73,11 @@ module Minitest
|
|
|
60
73
|
else
|
|
61
74
|
send(assertion || :assert_equal, expected, actual, message)
|
|
62
75
|
end
|
|
63
|
-
rescue Minitest::Assertion
|
|
64
|
-
raise
|
|
65
|
-
|
|
66
|
-
diff = Holdify::Pretty.call(expected, actual)
|
|
67
|
-
raise unless diff
|
|
68
|
-
|
|
69
|
-
msg = message ? "#{message}\n#{diff}" : diff
|
|
70
|
-
raise Minitest::Assertion, msg
|
|
76
|
+
rescue Minitest::Assertion => e
|
|
77
|
+
raise Minitest::Assertion, @hold.feedback(e.location, expected, actual, message)
|
|
71
78
|
end
|
|
72
79
|
|
|
73
|
-
if inspect
|
|
74
|
-
location = @hold.find_location
|
|
75
|
-
warn "[holdify] The value from #{location.path}:#{location.lineno} is:\n[holdify] => #{actual.inspect}"
|
|
76
|
-
end
|
|
80
|
+
@hold.warn_for(actual) if inspect
|
|
77
81
|
|
|
78
82
|
expected
|
|
79
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/feedback.rb
|
|
50
51
|
- lib/holdify/hold.rb
|
|
51
|
-
- lib/holdify/ledger.rb
|
|
52
|
-
- lib/holdify/pretty.rb
|
|
53
52
|
- lib/holdify/source.rb
|
|
54
53
|
- lib/holdify/store.rb
|
|
55
54
|
- lib/minitest-holdify.rb
|
data/lib/holdify/ledger.rb
DELETED
|
@@ -1,58 +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, line_width: 78) # Ensure 80 columns (including pretty gutter)
|
|
32
|
-
File.write(@path, content)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def load_and_align
|
|
38
|
-
{}.tap do |aligned|
|
|
39
|
-
data = YAML.unsafe_load_file(@path) || {}
|
|
40
|
-
data.group_by { |k, _| k.split.last }.each do |xxh, entries|
|
|
41
|
-
lines = @source.lines(xxh)
|
|
42
|
-
next if lines.empty?
|
|
43
|
-
|
|
44
|
-
# Position of the held lines compared to the source lines
|
|
45
|
-
stayed, moved = entries.map { |key, values| { line: key[/\d+/].to_i, values: values } }
|
|
46
|
-
.partition { |c| lines.include?(c[:line]) }
|
|
47
|
-
moved.sort_by! { |c| c[:line] }
|
|
48
|
-
|
|
49
|
-
# Align lines
|
|
50
|
-
lines.each do |line|
|
|
51
|
-
match = stayed.find { |c| c[:line] == line } || moved.shift
|
|
52
|
-
aligned[line] = match[:values] if match
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
data/lib/holdify/pretty.rb
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'yaml'
|
|
4
|
-
require 'tempfile'
|
|
5
|
-
require 'open3'
|
|
6
|
-
|
|
7
|
-
module Holdify
|
|
8
|
-
# Generates a pretty diff using git
|
|
9
|
-
module Pretty
|
|
10
|
-
RESET = "\e[0m"
|
|
11
|
-
RED_FG = "\e[31m"
|
|
12
|
-
GREEN_FG = "\e[32m"
|
|
13
|
-
|
|
14
|
-
G_BASE = "\e[100m #{RESET} ".freeze
|
|
15
|
-
G_EXP = "\e[41m\e[97m\e[1m-#{RESET} ".freeze
|
|
16
|
-
G_ACT = "\e[42m\e[97m\e[1m+#{RESET} ".freeze
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
def call(expected, actual)
|
|
20
|
-
exp_file = create_tempfile(expected)
|
|
21
|
-
act_file = create_tempfile(actual)
|
|
22
|
-
|
|
23
|
-
cmd = %W[git diff --no-index --word-diff=porcelain --unified=1000 #{exp_file.path} #{act_file.path}]
|
|
24
|
-
stdout, _stderr, _status = Open3.capture3(*cmd)
|
|
25
|
-
|
|
26
|
-
return nil if stdout.empty?
|
|
27
|
-
|
|
28
|
-
format(stdout)
|
|
29
|
-
rescue Errno::ENOENT
|
|
30
|
-
nil
|
|
31
|
-
ensure
|
|
32
|
-
exp_file&.close!
|
|
33
|
-
act_file&.close!
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def format(diff)
|
|
39
|
-
lines = diff.lines.map(&:chomp)
|
|
40
|
-
start = lines.index { |l| l.start_with?('@@') }
|
|
41
|
-
return nil unless start
|
|
42
|
-
|
|
43
|
-
output = ["#{G_EXP}#{RED_FG}Held (Expected)#{RESET}", "#{G_ACT}#{GREEN_FG}Current (Actual)#{RESET}"]
|
|
44
|
-
exp_buf = +''
|
|
45
|
-
act_buf = +''
|
|
46
|
-
|
|
47
|
-
lines[(start + 1)..].each do |line|
|
|
48
|
-
char = line[0]
|
|
49
|
-
text = line[1..]
|
|
50
|
-
|
|
51
|
-
# :nocov:
|
|
52
|
-
case char
|
|
53
|
-
# :nocov:
|
|
54
|
-
when ' '
|
|
55
|
-
exp_buf << text
|
|
56
|
-
act_buf << text
|
|
57
|
-
when '-'
|
|
58
|
-
exp_buf << "#{RED_FG}#{text}#{RESET}"
|
|
59
|
-
when '+'
|
|
60
|
-
act_buf << "#{GREEN_FG}#{text}#{RESET}"
|
|
61
|
-
when '~'
|
|
62
|
-
flush_buffers(output, exp_buf, act_buf)
|
|
63
|
-
exp_buf.clear
|
|
64
|
-
act_buf.clear
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
flush_buffers(output, exp_buf, act_buf) unless exp_buf.empty? && act_buf.empty?
|
|
69
|
-
|
|
70
|
-
output.join("\n")
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def flush_buffers(output, exp, act)
|
|
74
|
-
if exp == act
|
|
75
|
-
output << "#{G_BASE}#{exp}"
|
|
76
|
-
else
|
|
77
|
-
output << "#{G_EXP}#{exp}"
|
|
78
|
-
output << "#{G_ACT}#{act}"
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def create_tempfile(obj)
|
|
83
|
-
Tempfile.new(%w[holdify .yaml]).tap do |file|
|
|
84
|
-
file.write(YAML.dump(obj, line_width: 78)) # Ensure 80 columns (including pretty gutter)
|
|
85
|
-
file.flush
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|