minitest-holdify 1.0.3 → 1.1.1
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/hold.rb +15 -13
- data/lib/holdify/ledger.rb +60 -0
- data/lib/holdify/pretty.rb +90 -0
- data/lib/holdify/source.rb +32 -0
- data/lib/holdify/store.rb +10 -44
- data/lib/holdify.rb +10 -2
- data/lib/minitest/holdify_plugin.rb +22 -6
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48b28eeeaa320f75630822394f0f4b91a3025e081f085e39dee88ce264a5b2ab
|
|
4
|
+
data.tar.gz: fbc6c3b837a70c44d7359495c1e58809376022a6a458f504db4d7f06ff8585ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0accd428f401a337bbdbd8f9537b952c42e40508425d3353213b9795d2b0ecd4b617087d41f0abb79af508e156e7aa7149a6fe30e9cdff7b135c062f3b3caf32
|
|
7
|
+
data.tar.gz: c71c3caff7e00b1f8fb40ec5e871d3697cec7925a76559b1c8064a512bcc5a9285c150836a013edfa3c33325da9e3256096dfc27161251467ca817cfffbf8c19
|
data/lib/holdify/hold.rb
CHANGED
|
@@ -9,37 +9,39 @@ module Holdify
|
|
|
9
9
|
@test = test
|
|
10
10
|
@path, = test.method(test.name).source_location
|
|
11
11
|
@store = Holdify.stores[@path] ||= Store.new(@path)
|
|
12
|
-
@session = Hash.new { |h, k| h[k] = [] }
|
|
13
|
-
@forced = []
|
|
14
|
-
@added = []
|
|
12
|
+
@session = Hash.new { |h, k| h[k] = [] } # { line => [values] }
|
|
13
|
+
@forced = [] # [ "file:line" ]
|
|
14
|
+
@added = [] # [ "file:line" ]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def call(actual, force: false)
|
|
18
18
|
location = find_location
|
|
19
|
-
|
|
20
|
-
raise "Could not find holdify statement at line #{
|
|
19
|
+
line = location.lineno
|
|
20
|
+
raise "Could not find holdify statement at line #{line}" unless @store.sha_at(line)
|
|
21
21
|
|
|
22
|
-
@session[
|
|
23
|
-
@forced << "#{
|
|
22
|
+
@session[line] << actual
|
|
23
|
+
@forced << "#{@path}:#{line}" if force
|
|
24
24
|
|
|
25
25
|
return actual if force || Holdify.reconcile
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# Expected value
|
|
28
|
+
values = @store.get(line)
|
|
29
|
+
index = @session[line].size - 1
|
|
30
|
+
return values[index] if values && index < values.size
|
|
30
31
|
|
|
31
|
-
@added << "#{
|
|
32
|
+
@added << "#{@path}:#{line}"
|
|
32
33
|
actual
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def save
|
|
36
37
|
return unless @test.failures.empty?
|
|
37
38
|
|
|
38
|
-
@added.each
|
|
39
|
-
@session.each { |
|
|
39
|
+
@added.each { |loc| warn "[holdify] Held new value for #{loc}" } unless Holdify.quiet
|
|
40
|
+
@session.each { |line, values| @store.set(line, values) }
|
|
40
41
|
@store.save
|
|
41
42
|
end
|
|
42
43
|
|
|
44
|
+
# Find the location in the test that triggered the hold
|
|
43
45
|
def find_location
|
|
44
46
|
caller_locations.find do |location|
|
|
45
47
|
next unless location.path == @path
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
File.delete(@path) if Holdify.reconcile && File.exist?(@path)
|
|
13
|
+
@data = load_and_align
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(line) = @data[line]
|
|
17
|
+
|
|
18
|
+
def set(line, values) = (@data[line] = values)
|
|
19
|
+
|
|
20
|
+
def save
|
|
21
|
+
return FileUtils.rm_f(@path) if @data.empty?
|
|
22
|
+
|
|
23
|
+
output = {}
|
|
24
|
+
@data.keys.sort.each do |line|
|
|
25
|
+
sha = @source.sha_at(line)
|
|
26
|
+
next unless sha
|
|
27
|
+
|
|
28
|
+
output["L#{line} #{sha}"] = @data[line]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
content = YAML.dump(output, line_width: 78) # Ensure 80 columns (including pretty gutter)
|
|
32
|
+
return if File.exist?(@path) && File.read(@path) == content
|
|
33
|
+
|
|
34
|
+
File.write(@path, content)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def load_and_align
|
|
40
|
+
{}.tap do |aligned|
|
|
41
|
+
raw_data = (File.exist?(@path) && YAML.unsafe_load_file(@path)) || {}
|
|
42
|
+
raw_data.group_by { |k, _| k.split.last }.each do |sha, entries|
|
|
43
|
+
target_lines = @source.lines_with(sha)
|
|
44
|
+
next if target_lines.empty?
|
|
45
|
+
|
|
46
|
+
# Old data
|
|
47
|
+
candidates = entries.map { |key, values| { line: key[/\d+/].to_i, values: values } }
|
|
48
|
+
exact, moved = candidates.partition { |c| target_lines.include?(c[:line]) }
|
|
49
|
+
moved.sort_by! { |c| c[:line] }
|
|
50
|
+
|
|
51
|
+
# New aligned data
|
|
52
|
+
target_lines.each do |line|
|
|
53
|
+
match = exact.find { |c| c[:line] == line } || moved.shift
|
|
54
|
+
aligned[line] = match[:values] if match
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest/sha1'
|
|
4
|
+
|
|
5
|
+
module Holdify
|
|
6
|
+
# Represents the current state of the source file
|
|
7
|
+
class Source
|
|
8
|
+
def initialize(path) = (@line_sha, @sha_lines = parse(path))
|
|
9
|
+
|
|
10
|
+
def sha_at(line) = @line_sha[line]
|
|
11
|
+
|
|
12
|
+
def lines_with(sha) = @sha_lines[sha]
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def parse(path)
|
|
17
|
+
line_sha = {}
|
|
18
|
+
sha_lines = Hash.new { |h, k| h[k] = [] }
|
|
19
|
+
|
|
20
|
+
File.foreach(path).with_index(1) do |text, line|
|
|
21
|
+
content = text.strip
|
|
22
|
+
next if content.empty?
|
|
23
|
+
|
|
24
|
+
sha = Digest::SHA1.hexdigest(content)
|
|
25
|
+
line_sha[line] = sha
|
|
26
|
+
sha_lines[sha] << line
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
[line_sha, sha_lines]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/holdify/store.rb
CHANGED
|
@@ -1,54 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
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
|
-
|
|
11
|
-
@path = "#{source_path}#{CONFIG[:ext]}"
|
|
12
|
-
File.delete(@path) if Holdify.reconcile && File.exist?(@path)
|
|
10
|
+
extend Forwardable
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@source[lineno] = Digest::SHA1.hexdigest(content) unless content.empty?
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
@data = (File.exist?(@path) && YAML.unsafe_load_file(@path)) || {} # { key => [values] }
|
|
21
|
-
@index = {} # { id => "L123 id" }
|
|
22
|
-
|
|
23
|
-
valid_ids = @source.values
|
|
24
|
-
@data.keep_if do |key, _|
|
|
25
|
-
id = key.split.last
|
|
26
|
-
next false unless valid_ids.include?(id)
|
|
27
|
-
|
|
28
|
-
@index[id] = key
|
|
29
|
-
true
|
|
30
|
-
end
|
|
12
|
+
def initialize(path)
|
|
13
|
+
@source = Source.new(path)
|
|
14
|
+
@ledger = Ledger.new(path, @source)
|
|
31
15
|
end
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# Overwrite the entry for a given ID with a new list of values
|
|
37
|
-
def update(id, values)
|
|
38
|
-
new_key = "L#{@source.key(id)} #{id}"
|
|
39
|
-
old_key = @index[id]
|
|
40
|
-
@data.delete(old_key) if old_key && old_key != new_key
|
|
41
|
-
@data[@index[id] = new_key] = values
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def save
|
|
45
|
-
return FileUtils.rm_f(@path) if @data.empty?
|
|
46
|
-
|
|
47
|
-
sorted = @data.sort_by { |k, _| k[/\d+/].to_i }.to_h
|
|
48
|
-
content = YAML.dump(sorted, line_width: -1)
|
|
49
|
-
return if File.exist?(@path) && File.read(@path) == content
|
|
50
|
-
|
|
51
|
-
File.write(@path, content)
|
|
52
|
-
end
|
|
17
|
+
def_delegator :@source, :sha_at
|
|
18
|
+
def_delegators :@ledger, :get, :set, :save
|
|
53
19
|
end
|
|
54
20
|
end
|
data/lib/holdify.rb
CHANGED
|
@@ -3,14 +3,22 @@
|
|
|
3
3
|
require_relative 'holdify/store'
|
|
4
4
|
require_relative 'holdify/hold'
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# The container module
|
|
7
7
|
module Holdify
|
|
8
|
-
VERSION = '1.
|
|
8
|
+
VERSION = '1.1.1'
|
|
9
9
|
CONFIG = { ext: '.yaml' }.freeze
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
attr_accessor :reconcile, :quiet
|
|
13
|
+
attr_writer :pretty
|
|
13
14
|
|
|
14
15
|
def stores = @stores ||= {}
|
|
16
|
+
|
|
17
|
+
def pretty
|
|
18
|
+
return @pretty unless @pretty.nil?
|
|
19
|
+
|
|
20
|
+
@pretty = $stdout.tty? && !ENV.key?('NO_COLOR') && ENV['TERM'] != 'dumb' &&
|
|
21
|
+
system('git --version', out: File::NULL, err: File::NULL)
|
|
22
|
+
end
|
|
15
23
|
end
|
|
16
24
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'holdify'
|
|
4
|
+
require 'holdify/pretty'
|
|
4
5
|
|
|
5
6
|
# Implement the minitest plugin
|
|
6
7
|
module Minitest
|
|
@@ -10,6 +11,11 @@ module Minitest
|
|
|
10
11
|
Holdify.reconcile = true
|
|
11
12
|
Holdify.quiet = true
|
|
12
13
|
end
|
|
14
|
+
|
|
15
|
+
opts.on '--holdify-pretty', 'Format the stored values with pretty print' do
|
|
16
|
+
Holdify.pretty = true
|
|
17
|
+
end
|
|
18
|
+
|
|
13
19
|
opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
|
|
14
20
|
Holdify.quiet = true
|
|
15
21
|
end
|
|
@@ -40,10 +46,20 @@ module Minitest
|
|
|
40
46
|
assertion, message = message, assertion unless assertion.nil? || assertion.is_a?(Symbol)
|
|
41
47
|
expected = @hold.(actual, **)
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
begin
|
|
50
|
+
if actual.nil?
|
|
51
|
+
assert_nil expected, message
|
|
52
|
+
else
|
|
53
|
+
send(assertion || :assert_equal, expected, actual, message)
|
|
54
|
+
end
|
|
55
|
+
rescue Minitest::Assertion
|
|
56
|
+
raise unless Holdify.pretty
|
|
57
|
+
|
|
58
|
+
diff = Holdify::Pretty.call(expected, actual)
|
|
59
|
+
raise unless diff
|
|
60
|
+
|
|
61
|
+
msg = message ? "#{message}\n#{diff}" : diff
|
|
62
|
+
raise Minitest::Assertion, msg
|
|
47
63
|
end
|
|
48
64
|
|
|
49
65
|
if inspect
|
|
@@ -54,10 +70,10 @@ module Minitest
|
|
|
54
70
|
expected
|
|
55
71
|
end
|
|
56
72
|
|
|
57
|
-
#
|
|
73
|
+
# Force store the current value
|
|
58
74
|
def assert_hold!(*, **) = assert_hold(*, **, force: true)
|
|
59
75
|
|
|
60
|
-
#
|
|
76
|
+
# Print to STDERR the actual value
|
|
61
77
|
def assert_hold?(*, **) = assert_hold(*, **, inspect: true)
|
|
62
78
|
end
|
|
63
79
|
|
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.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Domizio Demichelis
|
|
@@ -34,6 +34,9 @@ files:
|
|
|
34
34
|
- LICENSE.txt
|
|
35
35
|
- lib/holdify.rb
|
|
36
36
|
- lib/holdify/hold.rb
|
|
37
|
+
- lib/holdify/ledger.rb
|
|
38
|
+
- lib/holdify/pretty.rb
|
|
39
|
+
- lib/holdify/source.rb
|
|
37
40
|
- lib/holdify/store.rb
|
|
38
41
|
- lib/minitest-holdify.rb
|
|
39
42
|
- lib/minitest/holdify_plugin.rb
|
|
@@ -59,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
59
62
|
- !ruby/object:Gem::Version
|
|
60
63
|
version: '0'
|
|
61
64
|
requirements: []
|
|
62
|
-
rubygems_version:
|
|
65
|
+
rubygems_version: 4.0.3
|
|
63
66
|
specification_version: 4
|
|
64
67
|
summary: Hardcoded values suck! Holdify them.
|
|
65
68
|
test_files: []
|