minitest-holdify 1.0.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 +7 -0
- data/LICENSE.txt +21 -0
- data/lib/holdify/store.rb +53 -0
- data/lib/holdify.rb +69 -0
- data/lib/minitest/holdify_plugin.rb +82 -0
- data/lib/minitest-holdify.rb +3 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cd9cfe38c2cb4dc5fb508261f9b89e107dcbcdb01902018ca30cd68f713bec4e
|
|
4
|
+
data.tar.gz: 487b7577edbeec62c7e2bd26f6dc7dc0af71fe6eae1915160265057c2efaf32e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b606c8de07fa255ee00025c26697b539092bd5ed44f5d01790ce96a115542186ab6371c3006eed5030f7f01c2f7264d9dcec4389964784daa112ada0ba61dbba
|
|
7
|
+
data.tar.gz: 2e448c696404d64f7a1491fb7fe6cdc90c4c4da7dc37df44f8a7e437d195b61d360bdc17b3751c61d4ceefcf3e38698aa64af4012406a8afa1731dec8e47e8dd
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Domizio Demichelis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
class Holdify
|
|
7
|
+
# A simple Hash-based store that syncs with a static source map
|
|
8
|
+
class Store
|
|
9
|
+
def initialize(source_path)
|
|
10
|
+
@path = "#{source_path}#{CONFIG[:ext]}"
|
|
11
|
+
File.delete(@path) if Holdify.rebuild && File.exist?(@path)
|
|
12
|
+
|
|
13
|
+
@source = {} # { lineno => id }
|
|
14
|
+
File.foreach(source_path).with_index(1) do |line, lineno|
|
|
15
|
+
content = line.strip
|
|
16
|
+
@source[lineno] = Digest::SHA1.hexdigest(content) unless content.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@data = (File.exist?(@path) && YAML.unsafe_load_file(@path)) || {} # { key => [values] }
|
|
20
|
+
@index = {} # { id => "L123 id" }
|
|
21
|
+
|
|
22
|
+
valid_ids = @source.values
|
|
23
|
+
@data.keep_if do |key, _|
|
|
24
|
+
id = key.split.last
|
|
25
|
+
next false unless valid_ids.include?(id)
|
|
26
|
+
|
|
27
|
+
@index[id] = key
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def id_at(lineno) = @source[lineno]
|
|
33
|
+
def stored(id) = @data[@index[id]]
|
|
34
|
+
|
|
35
|
+
# Overwrite the entry for a given ID with a new list of values
|
|
36
|
+
def update(id, values)
|
|
37
|
+
new_key = "L#{@source.key(id)} #{id}"
|
|
38
|
+
old_key = @index[id]
|
|
39
|
+
@data.delete(old_key) if old_key && old_key != new_key
|
|
40
|
+
@data[@index[id] = new_key] = values
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def save
|
|
44
|
+
return FileUtils.rm_f(@path) if @data.empty?
|
|
45
|
+
|
|
46
|
+
sorted = @data.sort_by { |k, _| k[/\d+/].to_i }.to_h
|
|
47
|
+
content = YAML.dump(sorted, line_width: -1)
|
|
48
|
+
return if File.exist?(@path) && File.read(@path) == content
|
|
49
|
+
|
|
50
|
+
File.write(@path, content)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/holdify.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest/sha1'
|
|
4
|
+
require_relative 'holdify/store'
|
|
5
|
+
|
|
6
|
+
# Add description
|
|
7
|
+
class Holdify
|
|
8
|
+
VERSION = '1.0.0'
|
|
9
|
+
CONFIG = { ext: '.yaml' }.freeze
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :rebuild, :quiet
|
|
13
|
+
|
|
14
|
+
def stores = @stores ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :forced
|
|
18
|
+
|
|
19
|
+
def initialize(test)
|
|
20
|
+
@test = test
|
|
21
|
+
@path, = test.method(test.name).source_location
|
|
22
|
+
@store = self.class.stores[@path] ||= Store.new(@path)
|
|
23
|
+
@session = Hash.new { |h, k| h[k] = [] }
|
|
24
|
+
@forced = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def hold(actual, force: false)
|
|
28
|
+
location = find_location
|
|
29
|
+
id = @store.id_at(location.lineno)
|
|
30
|
+
raise "Could not find holdify statement at line #{location.lineno}" unless id
|
|
31
|
+
|
|
32
|
+
@session[id] << actual
|
|
33
|
+
@forced << "#{location.path}:#{location.lineno}" if force
|
|
34
|
+
|
|
35
|
+
return actual if force || self.class.rebuild
|
|
36
|
+
|
|
37
|
+
stored = @store.stored(id)
|
|
38
|
+
index = @session[id].size - 1
|
|
39
|
+
return stored[index] if stored && index < stored.size
|
|
40
|
+
|
|
41
|
+
# :nocov:
|
|
42
|
+
warn "[holdify] Held new value for #{location.path}:#{location.lineno}" unless self.class.quiet
|
|
43
|
+
# :nocov:
|
|
44
|
+
actual
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def save
|
|
48
|
+
return unless @test.failures.empty?
|
|
49
|
+
|
|
50
|
+
@session.each { |id, values| @store.update(id, values) }
|
|
51
|
+
@store.save
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def find_location
|
|
55
|
+
caller_locations.find do |location|
|
|
56
|
+
next unless location.path == @path
|
|
57
|
+
|
|
58
|
+
label = location.base_label
|
|
59
|
+
# :nocov:
|
|
60
|
+
label = ::Regexp.last_match(1) if label.start_with?('block ') && label =~ / in (.+)$/
|
|
61
|
+
# :nocov:
|
|
62
|
+
|
|
63
|
+
label == @test.name ||
|
|
64
|
+
label == '<top (required)>' ||
|
|
65
|
+
label == '<main>' ||
|
|
66
|
+
label.start_with?('<class:', '<module:')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'holdify'
|
|
4
|
+
|
|
5
|
+
# Implement the minitest plugin
|
|
6
|
+
module Minitest
|
|
7
|
+
# Set the Holdify options
|
|
8
|
+
def self.plugin_holdify_options(opts, _options)
|
|
9
|
+
opts.on '--holdify-rebuild', 'Rebuild the stores with the current entries/values' do
|
|
10
|
+
Holdify.rebuild = true
|
|
11
|
+
Holdify.quiet = true
|
|
12
|
+
end
|
|
13
|
+
# :nocov:
|
|
14
|
+
opts.on '--holdify-quiet', 'Skip the warning on storing a new value' do
|
|
15
|
+
Holdify.quiet = true
|
|
16
|
+
end
|
|
17
|
+
# :nocov:
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Reopen the minitest class
|
|
21
|
+
class Test
|
|
22
|
+
# Create the @holdify instance for each test
|
|
23
|
+
def after_setup
|
|
24
|
+
super
|
|
25
|
+
@holdify ||= Holdify.new(self) # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Ensure store is tidied and saved after the test runs
|
|
29
|
+
def before_teardown
|
|
30
|
+
super
|
|
31
|
+
@holdify&.save
|
|
32
|
+
return unless @holdify&.forced&.any? && failures.empty?
|
|
33
|
+
|
|
34
|
+
msg = <<~MSG.chomp
|
|
35
|
+
[holdify] the value has been stored: remove the "!" suffix to pass the test
|
|
36
|
+
#{@holdify.forced.uniq.map { |l| " #{l}" }.join("\n")}
|
|
37
|
+
MSG
|
|
38
|
+
raise Minitest::Assertion, msg
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Reopen the minitest module
|
|
43
|
+
module Assertions
|
|
44
|
+
# Main assertion
|
|
45
|
+
def assert_hold(actual, *args)
|
|
46
|
+
@holdify ||= Holdify.new(self)
|
|
47
|
+
assertion, message = args
|
|
48
|
+
assertion, message = message, assertion unless assertion.nil? || assertion.is_a?(Symbol)
|
|
49
|
+
|
|
50
|
+
expected = @holdify.hold(actual)
|
|
51
|
+
if actual.nil?
|
|
52
|
+
assert_nil expected, message
|
|
53
|
+
else
|
|
54
|
+
send(assertion || :assert_equal, expected, actual, message)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Temporarily used to store the actual value, useful for reconciliation of expected changed values
|
|
59
|
+
def assert_hold!(actual, *)
|
|
60
|
+
@holdify ||= Holdify.new(self)
|
|
61
|
+
@holdify.hold(actual, force: true)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Temporarily used for development feedback to print to STDERR the actual value
|
|
65
|
+
def assert_hold_?(actual, *)
|
|
66
|
+
@holdify ||= Holdify.new(self)
|
|
67
|
+
location = @holdify.find_location
|
|
68
|
+
warn "[holdify] Actual value dumped from: #{location.path}:#{location.lineno}\n=> #{actual.inspect}"
|
|
69
|
+
@holdify.hold(actual)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Register expectations only if minitest/spec is loaded; ensure the right class in 6.0 and < 6.0
|
|
74
|
+
# :nocov:
|
|
75
|
+
if (expectation_class = defined?(Spec) && (defined?(Expectation) ? Expectation : Expectations))
|
|
76
|
+
%w[hold hold! hold_?].each do |suffix|
|
|
77
|
+
expectation_class.infect_an_assertion :"assert_#{suffix}", :"must_#{suffix}", :reverse
|
|
78
|
+
expectation_class.alias_method :"to_#{suffix}", :"must_#{suffix}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
# :nocov:
|
|
82
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: minitest-holdify
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Domizio Demichelis
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 5.0.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 5.0.0
|
|
26
|
+
description: Holdify eliminates the burden of maintaining large expected values into
|
|
27
|
+
your test files. It behaves as if the expected value were hardcoded inline, but
|
|
28
|
+
keeps it stored externally. This ensures your values hold true without polluting
|
|
29
|
+
your test files, and allows for effortless updates when your code changes.
|
|
30
|
+
email:
|
|
31
|
+
- dd.nexus@gmail.com
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- LICENSE.txt
|
|
37
|
+
- lib/holdify.rb
|
|
38
|
+
- lib/holdify/store.rb
|
|
39
|
+
- lib/minitest-holdify.rb
|
|
40
|
+
- lib/minitest/holdify_plugin.rb
|
|
41
|
+
homepage: https://github.com/ddnexus/holdify
|
|
42
|
+
licenses:
|
|
43
|
+
- MIT
|
|
44
|
+
metadata:
|
|
45
|
+
rubygems_mfa_required: 'true'
|
|
46
|
+
homepage_uri: https://github.com/ddnexus/holdify
|
|
47
|
+
bug_tracker_uri: https://github.com/ddnexus/holdify/issues
|
|
48
|
+
changelog_uri: https://github.com/ddnexus/holdify/blob/master/CHANGELOG.md
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '3.2'
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 3.6.9
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Hardcoded values suck! Hold them inline!
|
|
66
|
+
test_files: []
|