minitest-meaningful 0.3.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0537b67234cc8e5b97f42ac7117068392946f88ca607b2b7ff6aa0fff95b7f1f
4
- data.tar.gz: 49534eacac48a362598ca178ee5b91da40ab41da44f3ab132aecbdbfeb8d78c7
3
+ metadata.gz: 891d5286a476cddcc75becd847de84b03c50f372fbdc028b41370f458b370221
4
+ data.tar.gz: 4f3f88df084f83a9aefb3c855280c0cdd35eb026b1f9ed3fc953269c6fb248ae
5
5
  SHA512:
6
- metadata.gz: 4248b5451112ca9ff2dd6e96d22404b8b0aa8b4cd23ebf11f6596e365587c1737210ec33680029c3c1b2520d87aa7002f2df71ec00c4f786ea55e0a1f143a4ba
7
- data.tar.gz: b777b67082ea2a943b3c01d8fbb9b3760af8c2fdafe00cdec7ce209e0d7f8ab47e94c81c8cea663da0756b77d6f848937364ce9f7604ea63100ea70f8e8d06d8
6
+ metadata.gz: 5e59a86d5570dc1a7f6f2d10609ba17b698517ee6f5bda575e449c60fe0b9d8f855fadcce8ce2b02b96081f1b4572bb25cec716a988ddd842507cb1feb01c04e
7
+ data.tar.gz: c08ba688fe760b7f0b3f6eaad0dfa44c4dddbe4819762a4d10c6bb32364ee7110ef919d09b358615b4a216eaa0e99eff2c0ca1bce6cc9ff8d853a7cc135829d2
@@ -0,0 +1,14 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - uses: ruby/setup-ruby@v1
11
+ with:
12
+ ruby-version: '3.3'
13
+ bundler-cache: true
14
+ - run: bundle exec rake
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Marshall
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,76 +1,35 @@
1
1
  # Minitest::Meaningful
2
2
 
3
- This is a proof-of-concept Minitest plugin to help avoid writing completely meaningless tests. A meaningless test is one that always passes because of incorrect test setup. This plugin helps ensure tests aren't meaningless, by trying to make them fail.
3
+ TODO: Delete this and the text below, and describe your gem
4
4
 
5
- For example, in a Rails app we might have a test that looks like this:
6
-
7
- ```rb
8
- test "#visible_comments excludes hidden comments" do
9
- post = create(:post)
10
- comment = create(:comment, status: "hidden")
11
-
12
- refute_includes post.visible_comments, comment
13
- end
14
- ```
15
-
16
- The `comment` record is completely unrelated to the `post` record. It doesn't matter what the `status` is, nor does it really matter what the `#visible_comments` implementation looks like, there's no reason this comment would ever be returned. It always passes, it's a false positive test.
17
-
18
- This plugin adds an `important!` annotation to wrap the most important test setup variable. The test runner can then run the test both with and without evaluating that block. If the test passes when that `important!` block is not evaluated, then we know it is not meaningful.
19
-
20
- ```rb
21
- test "#visible_comments excludes hidden comments" do
22
- post = create(:post)
23
- comment = create(:comment)
24
- important! { comment.update!(status: "hidden") }
25
-
26
- refute_includes post.visible_comments, comment
27
- end
28
- ```
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/minitest/meaningful`. To experiment with that code, run `bin/console` for an interactive prompt.
29
6
 
30
7
  ## Installation
31
8
 
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
32
11
  Install the gem and add to the application's Gemfile by executing:
33
12
 
34
- $ bundle add minitest-meaningful
13
+ ```bash
14
+ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
15
+ ```
35
16
 
36
17
  If bundler is not being used to manage dependencies, install the gem by executing:
37
18
 
38
- $ gem install minitest-meaningful
39
-
40
- ## Usage
41
-
42
- Include the `Minitest::Meaningful` module in your test:
43
-
44
- ```rb
45
- class ExampleTest > Minitest::Test
46
- include Minitest::Meaningful
47
- end
19
+ ```bash
20
+ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
48
21
  ```
49
22
 
50
- Annotate the most important test setup variable:
51
-
52
- ```rb
53
- def test_visible_comments_excludes_hidden_comments
54
- post = create(:post)
55
- comment = create(:comment)
56
- important! { comment.update!(status: "hidden") }
23
+ ## Usage
57
24
 
58
- refute_includes post.visible_comments, comment
59
- end
60
- ```
25
+ TODO: Write usage instructions here
61
26
 
62
- Assert the test should fail:
27
+ ## Development
63
28
 
64
- ```rb
65
- assert_meaningful :test_visible_comments_excludes_hidden_comments
66
- ```
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
67
30
 
68
- Run the test with the `--meaningful` flag:
69
-
70
- ```sh
71
- $ ruby example_test.rb --meaningful
72
- ```
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
73
32
 
74
33
  ## Contributing
75
34
 
76
- This is definitely a hacky proof-of-concept. If you can think of a better way to integrate this into Minitest test suites, or a way to more robustly identify false-positive tests, please let me know! Also, I hate the name—please suggest something better!
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/minitest-meaningful.
data/Rakefile CHANGED
@@ -1,4 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- task default: %i[]
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Minitest
4
4
  module Meaningful
5
- VERSION = "0.3.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
@@ -1,91 +1,125 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "minitest"
3
4
  require_relative "meaningful/version"
4
5
 
5
6
  module Minitest
6
7
  module Meaningful
7
- class << self
8
- attr_accessor :enabled
8
+ class VacuousAssertion < Assertion; end
9
+
10
+ def assert(test, msg = nil)
11
+ result = super
12
+ unless [true, false, nil].include?(test)
13
+ vacuous! "assert passes for every #{test.class}. Use #{Meaningful.alternatives(test)} instead."
14
+ end
15
+ result
9
16
  end
10
17
 
11
- MultipleAnnotationsError = Class.new(StandardError)
18
+ def assert_output(stdout = nil, stderr = nil)
19
+ vacuous! "assert_output makes no assertions. Pass the expected stdout or stderr." if stdout.nil? && stderr.nil?
20
+ super
21
+ end
12
22
 
13
- module ClassMethods
14
- def runnable_methods
15
- if Meaningful.enabled
16
- meaningful_methods.keys
17
- else
18
- super
19
- end
23
+ def assert_raises(*exp)
24
+ error = super
25
+ if error.is_a?(NameError) && exp.grep(Module).none? { _1 <= NameError }
26
+ vacuous! "assert_raises caught #{error.class}: #{error.message}. Name the expected error class."
20
27
  end
28
+ error
29
+ end
21
30
 
22
- def meaningful_methods
23
- @meaningful_methods ||= {}
24
- end
31
+ def refute_equal(exp, act, msg = nil)
32
+ result = super
33
+ vacuous! "refute_equal passes for every value because the actual value is nil. Use assert_nil instead." if act.nil?
34
+ result
35
+ end
25
36
 
26
- def assert_meaningful(*names)
27
- names.each do |name|
28
- meaningful_methods[name.to_s] = caller
29
- end
37
+ def refute_includes(collection, obj, msg = nil)
38
+ result = super
39
+ if Meaningful.empty?(collection)
40
+ vacuous! "refute_includes passes for every object because the collection is empty. Use assert_empty instead."
30
41
  end
42
+ result
31
43
  end
32
44
 
33
- def self.included(base)
34
- base.extend(ClassMethods)
45
+ def refute_instance_of(cls, obj, msg = nil)
46
+ result = super
47
+ vacuous! "refute_instance_of passes for every class because the object is nil. Use assert_nil instead." if obj.nil?
48
+ result
35
49
  end
36
50
 
51
+ def refute_kind_of(cls, obj, msg = nil)
52
+ result = super
53
+ vacuous! "refute_kind_of passes for every class because the object is nil. Use assert_nil instead." if obj.nil?
54
+ result
55
+ end
37
56
 
38
- def run
39
- @should_fail = Meaningful.enabled
57
+ def refute_match(matcher, obj, msg = nil)
58
+ result = super
59
+ vacuous! "refute_match passes for every pattern because the subject is nil. Use assert_nil instead." if obj.nil?
60
+ vacuous! "refute_match passes because the subject is empty. Use assert_empty instead." if obj == ""
61
+ result
62
+ end
40
63
 
64
+ def refute_operator(o1, op, *)
65
+ result = super
66
+ if %i[include? member? key? has_key?].include?(op.to_sym) && Meaningful.empty?(o1)
67
+ vacuous! "refute_operator passes for every object because the receiver is empty. Use assert_empty instead."
68
+ end
69
+ result
70
+ end
71
+
72
+ def refute_respond_to(obj, *, **)
73
+ result = super
74
+ vacuous! "refute_respond_to passes for every method because the object is nil. Use assert_nil instead." if obj.nil?
75
+ result
76
+ end
77
+
78
+ def refute_same(exp, act, msg = nil)
79
+ result = super
80
+ vacuous! "refute_same passes because the objects are not equal. Use refute_equal instead." if exp != act
81
+ result
82
+ end
83
+
84
+ def after_teardown
41
85
  super
86
+ return unless assertions.zero? && failures.empty?
42
87
 
43
- if @should_fail
44
- if !@important_backtrace
45
- exception = Minitest::Assertion.new("Expected test to have `important!` setup.")
46
- exception.set_backtrace(self.class.meaningful_methods[name.to_s])
47
- self.failures << exception
48
- elsif !@did_fail
49
- exception = Minitest::Assertion.new("Expected test to fail without `important!` setup.")
50
- exception.set_backtrace(@important_backtrace)
51
- self.failures << exception
52
- end
53
- end
88
+ error = VacuousAssertion.new("Vacuous test: it makes no assertions.")
89
+ error.set_backtrace(method(name).source_location.join(":"))
90
+ raise error
91
+ end
54
92
 
55
- Result.from(self)
56
- rescue MultipleAnnotationsError
57
- exception = Minitest::Assertion.new("Test uses `important!` more than once.")
58
- exception.set_backtrace(@important_backtrace)
59
- self.failures << exception
93
+ def self.alternatives(value)
94
+ case value
95
+ when Integer then "refute_nil, assert_operator or assert_match"
96
+ when String then "refute_nil, refute_empty or assert_match"
97
+ when Array, Hash then "refute_nil or refute_empty"
98
+ else "refute_nil"
99
+ end
100
+ end
60
101
 
61
- Result.from(self)
102
+ def self.empty?(collection)
103
+ collection.respond_to?(:empty?) && collection.empty?
62
104
  end
63
105
 
64
- def capture_exceptions
65
- return super unless @should_fail
106
+ private
66
107
 
67
- yield
68
- rescue *Minitest::Test::PASSTHROUGH_EXCEPTIONS
69
- raise
70
- rescue Assertion => e
71
- @did_fail = true
108
+ def vacuous!(message)
109
+ raise VacuousAssertion, "Vacuous assertion: #{message}" if called_by_test?
72
110
  end
73
111
 
74
- def important!
75
- if @should_fail
76
- if @important_backtrace
77
- raise MultipleAnnotationsError
78
- else
79
- @important_backtrace = caller
80
- end
81
- else
82
- unless self.class.meaningful_methods.keys.include?(name.to_s)
83
- exception = Minitest::Assertion.new("Test uses `important!` without `assert_meaningful`.")
84
- raise exception
85
- end
112
+ # Minitest assertions call each other, and other gems call assertions
113
+ # from their own helpers. Only calls written in the test code are checked.
114
+ def called_by_test?
115
+ Thread.each_caller_location do |location|
116
+ path = location.path
117
+ next if path == __FILE__ || path.end_with?("minitest/spec.rb")
86
118
 
87
- yield
119
+ return Gem.path.none? { path.start_with?(_1) }
88
120
  end
89
121
  end
90
122
  end
123
+
124
+ Test.include Meaningful
91
125
  end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitest-meaningful
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Marshall
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-01 00:00:00.000000000 Z
11
+ date: 2026-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5'
19
+ version: '5.20'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5.20'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: '5'
32
+ version: '7'
27
33
  description:
28
34
  email:
29
35
  - thomas@thomasmarshall.com
@@ -31,11 +37,12 @@ executables: []
31
37
  extensions: []
32
38
  extra_rdoc_files: []
33
39
  files:
40
+ - ".github/workflows/ci.yml"
41
+ - LICENSE
34
42
  - README.md
35
43
  - Rakefile
36
44
  - lib/minitest/meaningful.rb
37
45
  - lib/minitest/meaningful/version.rb
38
- - lib/minitest/meaningful_plugin.rb
39
46
  homepage: https://github.com/thomasmarshall/minitest-meaningful
40
47
  licenses: []
41
48
  metadata:
@@ -49,15 +56,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
56
  requirements:
50
57
  - - ">="
51
58
  - !ruby/object:Gem::Version
52
- version: 2.6.0
59
+ version: 3.2.0
53
60
  required_rubygems_version: !ruby/object:Gem::Requirement
54
61
  requirements:
55
62
  - - ">="
56
63
  - !ruby/object:Gem::Version
57
64
  version: '0'
58
65
  requirements: []
59
- rubygems_version: 3.5.3
66
+ rubygems_version: 3.5.22
60
67
  signing_key:
61
68
  specification_version: 4
62
- summary: A minitest plugin to prevent false positive tests.
69
+ summary: A minitest plugin to fail vacuous assertions.
63
70
  test_files: []
@@ -1,13 +0,0 @@
1
- require "minitest/meaningful"
2
-
3
- module Minitest
4
- def self.plugin_meaningful_options(opts, options)
5
- opts.on "--meaningful", "Expect tests not to pass with important test setup removed." do
6
- options[:meaningful] = true
7
- end
8
- end
9
-
10
- def self.plugin_meaningful_init(options)
11
- Meaningful.enabled = options.fetch(:meaningful, false)
12
- end
13
- end