minitest-holdify 1.1.3 → 1.2.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/failure.rb +110 -0
- data/lib/holdify/hold.rb +7 -4
- data/lib/holdify/ledger.rb +29 -5
- data/lib/holdify/store.rb +1 -1
- data/lib/holdify.rb +6 -9
- data/lib/minitest/holdify_plugin.rb +9 -15
- metadata +2 -2
- 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: 056a8cc8b2a69276cf8adc189965b484cb9742b6c4e71efaca3b194b56bc1965
|
|
4
|
+
data.tar.gz: 1570f826edd18de9ee3dbbe6fa39d759f597ab5f5f4ebf452b015ae9cc26f240
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c6caaf67f67c9992df20d00ab2fc498365ae09f13225e7469aa370b68f0a42cbb77c97c7e68825c21b42600a606ef4c468bcec011c54e2380945bb8f9394741f
|
|
7
|
+
data.tar.gz: 30bf7d38f0df80f1a2b581c12ee7fbd1ca6811369fe4a2977e286d1876d37daabb1eb5cd8b8d9b81ceb40103dcf430003ef1ed8240db79ce5c5579680395ca7b
|
|
@@ -0,0 +1,110 @@
|
|
|
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/hold.rb
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
module Holdify
|
|
4
4
|
# The *_hold statement (Assertion/Expectation)
|
|
5
5
|
class Hold
|
|
6
|
-
attr_reader :forced
|
|
6
|
+
attr_reader :forced, :store
|
|
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
12
|
@session = Hash.new { |h, k| h[k] = [] } # { line => [values] }
|
|
13
|
-
@forced = [] # [
|
|
14
|
-
@added = [] # [
|
|
13
|
+
@forced = [] # [ lines ]
|
|
14
|
+
@added = [] # [ lines ]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def call(actual, force: false)
|
|
@@ -41,9 +41,12 @@ module Holdify
|
|
|
41
41
|
@session.each { |line, values| @store.set(line, values) }
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# The index of the current test/value
|
|
45
|
+
def current_index(line) = @session[line].size - 1
|
|
46
|
+
|
|
44
47
|
# Find the location in the test that triggered the hold
|
|
45
48
|
def find_location
|
|
46
|
-
caller_locations.find do |location|
|
|
49
|
+
caller_locations(2).find do |location|
|
|
47
50
|
next unless location.path == @path
|
|
48
51
|
|
|
49
52
|
label = location.base_label
|
data/lib/holdify/ledger.rb
CHANGED
|
@@ -28,10 +28,34 @@ module Holdify
|
|
|
28
28
|
output["L#{line} #{xxh}"] = @data[line]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
content = YAML.dump(output
|
|
31
|
+
content = YAML.dump(output)
|
|
32
32
|
File.write(@path, content)
|
|
33
33
|
end
|
|
34
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
|
+
|
|
35
59
|
private
|
|
36
60
|
|
|
37
61
|
def load_and_align
|
|
@@ -42,13 +66,13 @@ module Holdify
|
|
|
42
66
|
next if lines.empty?
|
|
43
67
|
|
|
44
68
|
# Position of the held lines compared to the source lines
|
|
45
|
-
stayed, moved = entries.map { |key, values| { line: key[/\d+/].to_i, values:
|
|
46
|
-
.partition {
|
|
47
|
-
moved.sort_by! {
|
|
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] }
|
|
48
72
|
|
|
49
73
|
# Align lines
|
|
50
74
|
lines.each do |line|
|
|
51
|
-
match = stayed.find {
|
|
75
|
+
match = stayed.find { _1[:line] == line } || moved.shift
|
|
52
76
|
aligned[line] = match[:values] if match
|
|
53
77
|
end
|
|
54
78
|
end
|
data/lib/holdify/store.rb
CHANGED
data/lib/holdify.rb
CHANGED
|
@@ -5,12 +5,16 @@ require_relative 'holdify/hold'
|
|
|
5
5
|
|
|
6
6
|
# The container module
|
|
7
7
|
module Holdify
|
|
8
|
-
VERSION = '1.
|
|
8
|
+
VERSION = '1.2.0'
|
|
9
9
|
CONFIG = { ext: '.yaml' }.freeze
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
attr_accessor :reconcile, :quiet
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
def config
|
|
15
|
+
@config ||= { git: system('git --version', out: File::NULL, err: File::NULL),
|
|
16
|
+
color: !ENV.key?('NO_COLOR') }
|
|
17
|
+
end
|
|
14
18
|
|
|
15
19
|
def stores(path = nil)
|
|
16
20
|
return @stores unless path
|
|
@@ -23,13 +27,6 @@ module Holdify
|
|
|
23
27
|
def persist_all!
|
|
24
28
|
@stores&.each_value(&:persist)
|
|
25
29
|
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,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'holdify'
|
|
4
|
-
require 'holdify/
|
|
4
|
+
require 'holdify/failure'
|
|
5
5
|
|
|
6
6
|
# Implement the minitest plugin
|
|
7
7
|
module Minitest
|
|
@@ -17,10 +17,6 @@ module Minitest
|
|
|
17
17
|
Holdify.quiet = true
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
opts.on '--holdify-pretty', 'Format the stored values with pretty print' do
|
|
21
|
-
Holdify.pretty = true
|
|
22
|
-
end
|
|
23
|
-
|
|
24
20
|
opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
|
|
25
21
|
Holdify.quiet = true
|
|
26
22
|
end
|
|
@@ -37,10 +33,9 @@ module Minitest
|
|
|
37
33
|
return unless @hold.forced.any?
|
|
38
34
|
|
|
39
35
|
path, = method(name).source_location
|
|
40
|
-
msg
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
MSG
|
|
36
|
+
msg = +%([holdify] the value has been stored: remove the "!" suffix to pass the test\n)
|
|
37
|
+
msg << @hold.forced.uniq.map { |line| " #{path}:#{line}" }.join("\n")
|
|
38
|
+
|
|
44
39
|
raise Minitest::Assertion, msg
|
|
45
40
|
end
|
|
46
41
|
end
|
|
@@ -60,13 +55,12 @@ module Minitest
|
|
|
60
55
|
else
|
|
61
56
|
send(assertion || :assert_equal, expected, actual, message)
|
|
62
57
|
end
|
|
63
|
-
rescue Minitest::Assertion
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
raise unless diff
|
|
58
|
+
rescue Minitest::Assertion => e
|
|
59
|
+
location = @hold.find_location
|
|
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
|
|
68
62
|
|
|
69
|
-
msg = message ? "#{message}\n#{
|
|
63
|
+
msg = message ? "#{message}\n#{hold_msg}" : hold_msg
|
|
70
64
|
raise Minitest::Assertion, msg
|
|
71
65
|
end
|
|
72
66
|
|
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.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Domizio Demichelis
|
|
@@ -47,9 +47,9 @@ extra_rdoc_files: []
|
|
|
47
47
|
files:
|
|
48
48
|
- LICENSE.txt
|
|
49
49
|
- lib/holdify.rb
|
|
50
|
+
- lib/holdify/failure.rb
|
|
50
51
|
- lib/holdify/hold.rb
|
|
51
52
|
- lib/holdify/ledger.rb
|
|
52
|
-
- lib/holdify/pretty.rb
|
|
53
53
|
- lib/holdify/source.rb
|
|
54
54
|
- lib/holdify/store.rb
|
|
55
55
|
- lib/minitest-holdify.rb
|
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
|