mrspec 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6cbbcda0055d2073b1bc987184afd21145c4a527
4
+ data.tar.gz: 6131f41d4ffd68a89a00b1ba92f70f1e6b87bccb
5
+ SHA512:
6
+ metadata.gz: 4f1e700ae9c81848aede3a469920d55685152b22e2d68bc65b9c65592a97f99c229af26c3d1adaa5035e7577cc8073696d0997fe42bca983f271c3d2b49ea283
7
+ data.tar.gz: b9cd655c7c06136b3abd010c66a4b70e2525a21f458971e2d5b6b6c2f3a9938ef39b02892b185217809d733edbd90ec762eb2f00b1d13dcc2c9e0dd56fa56253
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ proving_grounds
2
+ *.gem
3
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ script: cucumber
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.2
7
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Readme.md ADDED
@@ -0,0 +1,256 @@
1
+ [![Build Status](https://secure.travis-ci.org/JoshCheek/mrspec.png?branch=master)](http://travis-ci.org/JoshCheek/mrspec)
2
+
3
+ mrspec
4
+ ======
5
+
6
+ Minitest and RSpec, sitting in a tree...
7
+
8
+ Runs Minitest tests using RSpec's runner.
9
+ Also runs RSpec's tests, so if you want to use them side-by-side,
10
+ this will do it.
11
+
12
+
13
+ Examples
14
+ --------
15
+
16
+
17
+ ### Run specs and tests in tandem
18
+
19
+ It matches `test/*_test.rb`, `test/test_*.rb`, `spec/*_spec.rb`.
20
+ The RSpec group description is the class name without `Test` prefix/suffix.
21
+ The example name is the method name without the `test_` prefix,
22
+ and with underscores switched to spaces.
23
+ It finds test classes and test methods by asking Minitest what it is tracking.
24
+
25
+ ```ruby
26
+ # file: spec/a_spec.rb
27
+ RSpec.describe 'An RSpec test' do
28
+ it('does rspec things') { }
29
+ end
30
+
31
+ # file: test/b_test.rb
32
+ class AMinitestTest < Minitest::Test
33
+ def test_it_does_minitesty_things
34
+ end
35
+ end
36
+
37
+ # file: test/test_c.rb
38
+ class TestSomethingElse < Minitest::Test
39
+ def some_helper_method # won't show up
40
+ end
41
+
42
+ def test_this_also_does_minitesty_things
43
+ end
44
+ end
45
+
46
+ # Added because Minitest::Runnable knows about it
47
+ class AnotherTestWithNeitherThePrefixNorTheSuffix < Minitest::Test
48
+ # This causes Minitest to consider it a test, so we consider it one
49
+ def self.runnables
50
+ ['is_this_a_test_yes_it_is']
51
+ end
52
+
53
+ def is_this_a_test_yes_it_is
54
+ end
55
+ end
56
+
57
+ # Ignored b/c Minitest::Runnable doesn't know about it
58
+ class NotATest
59
+ def test_whatevz
60
+ end
61
+ end
62
+
63
+ # file: test/d_spec.rb
64
+ require 'minitest/spec'
65
+ describe 'I am a minitest spec' do
66
+ it 'does minitesty things' do
67
+ assert_includes self.class.ancestors, Minitest::Spec
68
+ end
69
+ end
70
+ ```
71
+
72
+ ![file patterns](https://s3.amazonaws.com/josh.cheek/mrspec/file-patterns.png)
73
+
74
+
75
+ ### Failures, Errors, Skips
76
+
77
+ It understands Minitest skips and errors/failures.
78
+
79
+ ```ruby
80
+ # file: various_errors.rb
81
+ class VariousErrors < Minitest::Test
82
+ def test_this_passes() assert true end
83
+ def test_they_arent_equal() assert_equal 1, 2 end
84
+ def test_is_not_included() assert_includes %w[a b c], 'd' end
85
+ def test_skipped_because…_reasons() skip end
86
+ end
87
+ ```
88
+
89
+ I used `--format progress` here, because there's enough errors that my default documentation
90
+ formatter makes it spammy >.<
91
+
92
+ ![various errors](https://s3.amazonaws.com/josh.cheek/mrspec/various-errors2.png)
93
+
94
+
95
+ ### Fail Fast and filtering
96
+
97
+ The `--fail-fast` flag is a favourite of mine. It continues running tests until it sees a failure, then it stops.
98
+
99
+ We can also use tags to filter which tests to run.
100
+ Mrspec adds RSpec metadata to Minitest classes and tests,
101
+ the metadata behaves as a tag.
102
+
103
+ The best thing about tags is they're easy to add,
104
+ and they continue to apply to the same test, when it moves around
105
+ (line numbers change), They stay correct even if I rename it!
106
+ I won't have to tweak my command-line invocation until I've got the test passing!
107
+
108
+
109
+ ```ruby
110
+ # file: test.rb
111
+
112
+ # Here, we tag the `NoFailures` class with `them_passing_tests`
113
+ # And the `TwoFailures` class with `them_failing_tests`
114
+ class NoFailures < Minitest::Test
115
+ classmeta them_passing_tests: true
116
+
117
+ def test_1() end
118
+ def test_2() end
119
+ end
120
+
121
+ class TwoFailures < Minitest::Test
122
+ classmeta them_failing_tests: true
123
+
124
+ # I like short tagnames, b/c usually my use is transient.
125
+ # I just keep them around until they're fixed.
126
+ meta f1: true
127
+ def test_3
128
+ raise 'first failure'
129
+ end
130
+
131
+ meta f2: true
132
+ def test_4
133
+ raise 'second failure'
134
+ end
135
+ end
136
+ ```
137
+
138
+ ![Examples of Tagging](https://s3.amazonaws.com/josh.cheek/mrspec/tagging.png)
139
+
140
+
141
+
142
+ Why?
143
+ ----
144
+
145
+ The default way to run minitest tests is with Rake.
146
+ And if you have multiple suites, that can be nice,
147
+ or if you already use Rake, then it's not adding a new dependency.
148
+ But here are some frustrations I have with it as a test runner:
149
+
150
+ 1. It's not a test runner, it's a build tool. As such, it's not well suited to this task.
151
+ 1. I shouldn't have to add a dependency on Rake just to run my tests.
152
+ 1. The `Rake::TestTask` is confoundingly opaque (I rant about it [here](https://github.com/stripe/stripe-ruby/pull/144#issuecomment-48810307))
153
+ 1. It ultimately just [shells out](https://github.com/rspec/rspec-core/blob/3145e2544e1825bc754d0986e893664afe19abf5/lib/rspec/core/rake_task.rb#L70)
154
+ (as does the [RSpec one](https://github.com/ruby/rake/blob/e644af3a09659c7e04245186607091324d8816e9/lib/rake/testtask.rb#L104),
155
+ so why do I need layers of translation and abstraction between me and a command-line invocation?
156
+ And what's the value of adding an entire process between me and my tests?
157
+ Think how long Rails takes to start up, now imagine paying that twice every time you want to run your tests!
158
+ 1. It makes it incredibly difficult to dynamically alter my test invoation.
159
+ With Minitest, you can pass `-n test_something` and it will only run the test named `test_something`,
160
+ but now I have to edit code tomake that happen (or write code to uese
161
+ environment variables, which is better, but still cumbersome,
162
+ and doesn't fail noisily if I mistype it)
163
+
164
+ Furthermore, if someone doesn't know about the test task, or it seems formidable, as it often does to new students
165
+ (I'm a [teacher](http://turing.io/team)), then they won't use it. They instead run files one at a time.
166
+ When I go to run the tests, there's just no way built in to run them all. I wind up having to craft clever command-line invocations
167
+ using custom tools ([1](https://github.com/JoshCheek/dotfiles/blob/master/bin/ff),
168
+ [2](https://github.com/JoshCheek/dotfiles/blob/master/bin/prepend)).
169
+
170
+ ```sh
171
+ $ ff test '\.rb' | prepend -r | xargs ruby -e ''
172
+ ```
173
+
174
+ Oftentimes, this is the first time they've all been run together, and we find out they haven't run a test in a long time,
175
+ because it's too cumbersome for them (they're not good with their tools yet), it's failing or worse, syntactically invalid.
176
+ Maybe they even know it, but they run them one at a time, so it hasn't been a problem for them yet.
177
+
178
+ Anyway, all of this is to say that Minitest needs a runner.
179
+ I hear Rails is working on one, but I don't know when that'll be available,
180
+ and who knows if they'll write it well enough to be used outside of Rails.
181
+
182
+ But the RSpec runner is very nice, it has a lot of features that I use frequently.
183
+ Someone suggested running Minitest with the RSpec runner (see attribution section),
184
+ and I thought that was an interesting idea that could have value if it worked.
185
+ ...so, here we are.
186
+
187
+ Nuances
188
+ -------
189
+
190
+ Changes the default pattern to look for any files suffixed with `_test.rb` or `_spec.rb`, or prefixed with `test_`
191
+ (RSpec, by itself, only looks for suffixes of `_spec.rb`).
192
+
193
+ Changes the default search directories to be `test` and `spec`
194
+ (RSpec, by itself, only looks in `spec`).
195
+
196
+ Turns off monkey patching, so you cannot use RSPec's toplevel describe, or `should`.
197
+ There are 2 reasons for this:
198
+
199
+ 1. It conflicts with `Minitest::Spec`'s definition of `Kernel#describe`
200
+ ([here](https://github.com/seattlerb/minitest/blob/f1081566ec6e9e391628bde3a26fb057ad2576a8/lib/minitest/spec.rb#L71)).
201
+ And must be preemptively turned off, because after-the-fact disabling
202
+ causes it to be undefined on both `main` and `Module` ([here](https://github.com/rspec/rspec-core/blob/3145e2544e1825bc754d0986e893664afe19abf5/lib/rspec/core/dsl.rb#L72)),
203
+ which means that even if you don't use it, it will still interfere with `Minitest::Spec`
204
+ (removing methods allows method lookup to find superclass definitions,
205
+ but undefining them ends method lookup.)
206
+ 2. You should just not do that in general. Monkey patching is a bad plan, all around,
207
+ just use the namespaced methods, or create your own methods to wrap the assertion syntax.
208
+ I'm looking forward to when this feature is removed altogether.
209
+
210
+
211
+ Running the tests
212
+ -----------------
213
+
214
+ ```sh
215
+ $ bundle
216
+ $ bundle exec cucumber
217
+ ```
218
+
219
+ Why are all the tests written in Cucumber?
220
+ Well... mostly just b/c I initially wrote this as a script for my dotfiles,
221
+ which I mostly test with Cucumber and [Haiti](https://github.com/JoshCheek/haiti),
222
+ as they are usually heavily oriented towards integration,
223
+ and often not written in Ruby.
224
+
225
+
226
+ Attribution
227
+ -----------
228
+
229
+ Idea from [e2](https://github.com/e2), proposed [here](https://github.com/rspec/rspec-core/issues/1786).
230
+ Iniitial code was based off of [this gist](https://gist.github.com/e2/bcd2be81b4ac28c85ea0)
231
+
232
+
233
+ MIT License
234
+ -----------
235
+
236
+ ```
237
+ Copyright (c) 2015 Josh Cheek
238
+
239
+ Permission is hereby granted, free of charge, to any person obtaining a copy
240
+ of this software and associated documentation files (the "Software"), to deal
241
+ in the Software without restriction, including without limitation the rights
242
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
243
+ copies of the Software, and to permit persons to whom the Software is
244
+ furnished to do so, subject to the following conditions:
245
+
246
+ The above copyright notice and this permission notice shall be included in
247
+ all copies or substantial portions of the Software.
248
+
249
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
250
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
251
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
252
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
253
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
254
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
255
+ THE SOFTWARE.
256
+ ```
data/bin/mrspec ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require 'mrspec'
5
+
6
+ # All your config are belong to us
7
+ RSpec.configuration = MRspec::Configuration.new
8
+
9
+ # Use our option parser instead of RSpec's
10
+ RSpec::Core::Parser.parser_method = RSpec::Core::Parser.instance_method :mrspec_parser
11
+
12
+ # Run tests with our runner at program exit
13
+ MRspec::Runner.autorun
14
+
15
+ # Disable RSpec and Minitest's autorun methods
16
+ # so that loading tests doesn't lead to multiple runners
17
+ RSpec::Core::Runner.define_singleton_method(:autorun) { }
18
+ Minitest.define_singleton_method(:autorun) { }
@@ -0,0 +1,475 @@
1
+ Feature: mrspec
2
+ Minitest doesn't have a runner, but a runner would be really nice.
3
+ RSpec has a nice runner... so lets join them together!
4
+
5
+ Scenario: Finds spec/**/*_spec.rb and test/**/*_test.rb and test/**/test_*.rb
6
+ Given the file "spec/a_spec.rb":
7
+ """
8
+ RSpec.describe 'a' do
9
+ it('passes') { }
10
+ end
11
+ """
12
+ And the file "spec/dir/b_spec.rb":
13
+ """
14
+ RSpec.describe 'b' do
15
+ it('passes') { }
16
+ end
17
+ """
18
+ And the file "test/c_test.rb":
19
+ """
20
+ class CTest < Minitest::Test
21
+ def test_passes
22
+ end
23
+ end
24
+ """
25
+ And the file "test/dir/d_test.rb":
26
+ """
27
+ class DTest < Minitest::Test
28
+ def test_passes
29
+ end
30
+ end
31
+ """
32
+ And the file "test/dir/test_e.rb":
33
+ """
34
+ class ETest < Minitest::Test
35
+ def test_passes
36
+ end
37
+ end
38
+ """
39
+ And the file "test/a_test_file.rb":
40
+ """
41
+ raise "I should not be loaded!"
42
+ """
43
+ And the file "spec/a_spec_file.rb":
44
+ """
45
+ raise "I should not be loaded!"
46
+ """
47
+ When I run 'mrspec -f json'
48
+ Then the program ran successfully
49
+ And stdout includes "5 examples"
50
+ And stdout includes "0 failures"
51
+
52
+
53
+ Scenario: Registers minitest tests as RSpec tests, recording skips, passes, errors, failures
54
+ Given the file "some_test.rb":
55
+ """
56
+ class LotaStuffsTest < Minitest::Test
57
+ def test_passes
58
+ end
59
+
60
+ def test_fails
61
+ assert_equal 1, 2
62
+ end
63
+
64
+ def test_errors
65
+ raise 'omg'
66
+ end
67
+
68
+ def test_skips
69
+ skip
70
+ end
71
+ end
72
+ """
73
+ When I run "mrspec some_test.rb --no-color --format progress"
74
+
75
+ # counts correctly
76
+ Then stdout includes "4 examples"
77
+ And stdout includes "2 failures"
78
+ And stdout includes "1 pending"
79
+ And stdout does not include "No examples found"
80
+
81
+ # displays the failed assertion, not an error
82
+ And stdout includes "Expected: 1"
83
+ And stdout includes "Actual: 2"
84
+
85
+ # displays the test's code, not the integration code
86
+ And stdout includes "raise 'omg'"
87
+ And stdout includes "assert_equal 1, 2"
88
+ And stdout does not include "Minitest.run_one_method"
89
+
90
+
91
+ Scenario: Works with Minitest::Test, choosing intelligent names
92
+ Given the file "some_test.rb":
93
+ """
94
+ class MyClass1Test < Minitest::Test
95
+ def test_it_does_stuff
96
+ end
97
+ end
98
+
99
+ class TestMyClass2 < Minitest::Test
100
+ def test_it_does_stuff
101
+ end
102
+ end
103
+ """
104
+ When I run "mrspec some_test.rb -f json"
105
+ Then the program ran successfully
106
+ And stdout includes '"full_description":"MyClass1 it does stuff"'
107
+ And stdout includes '"full_description":"MyClass2 it does stuff"'
108
+
109
+
110
+ Scenario: Works with Minitest::Spec, choosing intelligent names
111
+ Given the file "some_spec.rb":
112
+ """
113
+ require 'minitest/spec'
114
+ describe 'the description' do
115
+ it 'the example' do
116
+ assert true
117
+ if kind_of? Minitest::Spec
118
+ puts "I am defined by Minitest::Spec"
119
+ end
120
+ end
121
+ end
122
+ """
123
+ When I run "mrspec some_spec.rb -f json"
124
+ Then stdout includes "1 example"
125
+ And stdout includes "0 failures"
126
+ And stdout includes "I am defined by Minitest::Spec"
127
+ And stdout includes '"description":"the example"'
128
+ And stdout includes '"full_description":"the description the example"'
129
+
130
+
131
+ Scenario: Filters the runner and minitest code out of the backtrace do
132
+ Given the file "some_test.rb":
133
+ """
134
+ class LotaStuffsTest < Minitest::Test
135
+ def test_errors
136
+ raise "zomg"
137
+ end
138
+ end
139
+ """
140
+ When I run "mrspec some_test.rb"
141
+ Then stdout does not include "minitest"
142
+ And stdout does not include "mrspec.rb"
143
+ And stdout does not include "bin/mrspec"
144
+
145
+
146
+ Scenario: --fail-fast flag
147
+ Given the file "fails_fast_test.rb":
148
+ """
149
+ class TwoFailures < Minitest::Test
150
+ i_suck_and_my_tests_are_order_dependent!
151
+ def test_1
152
+ raise
153
+ end
154
+ def test_2
155
+ raise
156
+ end
157
+ end
158
+ """
159
+ When I run 'mrspec fails_fast_test.rb --fail-fast'
160
+ Then stdout includes "1 example"
161
+
162
+
163
+ Scenario: -e flag
164
+ Given the file "spec/first_spec.rb":
165
+ """
166
+ RSpec.describe 'a' do
167
+ example('b') { }
168
+ end
169
+ """
170
+ Given the file "test/first_test.rb":
171
+ """
172
+ class FirstTest < Minitest::Test
173
+ def test_1
174
+ end
175
+ end
176
+ """
177
+ And the file "test/second_test.rb":
178
+ """
179
+ class SecondTest < Minitest::Test
180
+ def test_2
181
+ end
182
+ end
183
+ """
184
+ When I run 'mrspec -e Second'
185
+ Then stdout includes "1 example"
186
+ And stdout does not include "2 examples"
187
+
188
+
189
+ Scenario: Passing a filename overrides the default pattern
190
+ Given the file "spec/first_spec.rb":
191
+ """
192
+ RSpec.describe 'first spec' do
193
+ example('rspec 1') { }
194
+ end
195
+ """
196
+ Given the file "test/first_test.rb":
197
+ """
198
+ class FirstTest < Minitest::Test
199
+ def test_minitest_1
200
+ end
201
+ end
202
+ """
203
+ And the file "test/second_test.rb":
204
+ """
205
+ class SecondTest < Minitest::Test
206
+ def test_minitest_2
207
+ end
208
+ end
209
+ """
210
+ When I run 'mrspec -f d test/second_test.rb'
211
+ Then stdout includes "1 example"
212
+ And stdout includes "minitest 2"
213
+ And stdout does not include "rspec 1"
214
+ And stdout does not include "minitest 1"
215
+
216
+
217
+ Scenario: Can add metadata to examples, ie run only tagged tests
218
+ Given the file "test/tag_test.rb":
219
+ """
220
+ class TagTest < Minitest::Test
221
+ meta first: true
222
+ def test_1
223
+ puts "ran test 1"
224
+ end
225
+
226
+ # multiple tags in meta, and aggregated across metas
227
+ meta second: true, second2: true
228
+ meta second3: true
229
+ def test_2
230
+ puts "ran test 2"
231
+ end
232
+
233
+ def test_3
234
+ puts "ran test 3"
235
+ end
236
+ end
237
+ """
238
+
239
+ # only test_1 is tagged w/ first
240
+ When I run 'mrspec test/tag_test.rb -t first'
241
+ Then the program ran successfully
242
+ Then stdout includes "1 example"
243
+ And stdout includes "ran test 1"
244
+ And stdout does not include "ran test 2"
245
+ And stdout does not include "ran test 3"
246
+
247
+ # test_2 is tagged w/ second, and second2 (multiple tags in 1 meta)
248
+ When I run 'mrspec test/tag_test.rb -t second'
249
+ Then stdout includes "1 example"
250
+ And stdout includes "ran test 2"
251
+ And stdout does not include "ran test 1"
252
+ And stdout does not include "ran test 3"
253
+
254
+ When I run 'mrspec test/tag_test.rb -t second2'
255
+ Then stdout includes "1 example"
256
+ And stdout includes "ran test 2"
257
+ And stdout does not include "ran test 1"
258
+ And stdout does not include "ran test 3"
259
+
260
+ # test_2 is tagged with second3 (consolidates metadata until they are used)
261
+ When I run 'mrspec test/tag_test.rb -t second3'
262
+ Then stdout includes "1 example"
263
+ And stdout includes "ran test 2"
264
+ And stdout does not include "ran test 1"
265
+ And stdout does not include "ran test 3"
266
+
267
+ # for sanity, show that test_3 is actually a test, just not tagged (metadata gets cleared)
268
+ When I run 'mrspec test/tag_test.rb'
269
+ Then stdout includes "3 examples"
270
+ And stdout includes "ran test 1"
271
+ And stdout includes "ran test 2"
272
+ And stdout includes "ran test 3"
273
+
274
+
275
+ Scenario: Can add metadata to groups
276
+ Given the file "tag_groups.rb":
277
+ """
278
+ class Tag1Test < Minitest::Test
279
+ classmeta tag1: true
280
+
281
+ meta tag2: true
282
+ def test_tagged_with_1_and_2
283
+ end
284
+
285
+ def test_tagged_with_1_only
286
+ end
287
+ end
288
+
289
+ class UntaggedTest < Minitest::Test
290
+ def test_untagged
291
+ end
292
+ end
293
+ """
294
+
295
+ # tag1 runs all tests in Tag1Test (b/c the tag is on the class)
296
+ When I run 'mrspec -f d -t tag1 tag_groups.rb'
297
+ Then the program ran successfully
298
+ And stdout includes "tagged with 1 and 2"
299
+ And stdout includes "tagged with 1 only"
300
+ And stdout does not include "untagged"
301
+
302
+ # tag2 runs only Tag1Test#test_tagged_with_1_and_2 (b/c the tag is on the method)
303
+ When I run 'mrspec -f d -t tag2 tag_groups.rb'
304
+ Then the program ran successfully
305
+ And stdout includes "tagged with 1 and 2"
306
+ And stdout does not include "tagged with 1 only"
307
+ And stdout does not include "untagged"
308
+
309
+ # no tags runs all tests (ignores all tagging)
310
+ When I run 'mrspec -f d tag_groups.rb'
311
+ Then the program ran successfully
312
+ And stdout includes "tagged with 1 and 2"
313
+ And stdout includes "tagged with 1 only"
314
+ And stdout includes "untagged"
315
+
316
+ Scenario: Intelligently formats Minitest's assertions
317
+ Given the file "test/some_assertions.rb":
318
+ """
319
+ RSpec.describe 'a' do
320
+ it('fails1') { expect('rspec-1').to eq 'rspec-2' }
321
+ it('fails2') { expect(%w[rspec a b c]).to include 'd' }
322
+ end
323
+
324
+ class A < Minitest::Test
325
+ def test_fails1() assert_equal 'minitest-1', 'minitest-2' end
326
+ def test_fails2() assert_includes %w[minitest a b c], 'd' end
327
+ end
328
+ """
329
+ When I run 'mrspec --no-color test/some_assertions.rb'
330
+ # RSpec eq
331
+ Then stdout includes 'expected: "rspec-2"'
332
+ And stdout includes ' got: "rspec-1"'
333
+ # Minitest assert_equal
334
+ And stdout includes 'Expected: "minitest-1"'
335
+ And stdout includes ' Actual: "minitest-2"'
336
+
337
+ # RSpec includes
338
+ And stdout includes 'expected ["rspec", "a", "b", "c"] to include "d"'
339
+ # Minitest assert_includes
340
+ And stdout includes 'Expected ["minitest", "a", "b", "c"] to include "d"'
341
+
342
+ # Doesn't print Minitest::Assertion class, as if it's an exception
343
+ And stdout does not include "Minitest::Assertion"
344
+
345
+ Scenario: Respects Minitest's lifecycle hooks
346
+ Given the file "test/lifecycle_test.rb":
347
+ """
348
+ class A < Minitest::Test
349
+ %w(before_teardown teardown after_teardown before_setup setup after_setup).shuffle.each do |methodname|
350
+ define_method methodname do
351
+ @order ||= []
352
+ @order << methodname
353
+ end
354
+ end
355
+
356
+ def test_1
357
+ @order << :test_1
358
+ at_exit { puts "[#{@order.join " "}]" }
359
+ end
360
+
361
+ def test_2
362
+ @order << :test_2
363
+ at_exit { puts "[#{@order.join " "}]" }
364
+ end
365
+ end
366
+ """
367
+ When I run "mrspec test/lifecycle_test.rb"
368
+ Then the program ran successfully
369
+ And stdout includes "[before_setup setup after_setup test_1 before_teardown teardown after_teardown]"
370
+ And stdout includes "[before_setup setup after_setup test_2 before_teardown teardown after_teardown]"
371
+
372
+
373
+ Scenario: Doesn't get fucked up by Minitest autorunning
374
+ Given the file "requires_minitest_autorun.rb":
375
+ """
376
+ require 'minitest/autorun'
377
+ class A < Minitest::Test
378
+ def test_a
379
+ p caller.last # will tell us which lib is running this test
380
+ end
381
+ end
382
+ """
383
+ When I run "mrspec requires_minitest_autorun.rb"
384
+ Then stdout includes "rspec/core/runner.rb"
385
+ And stdout does not include "lib/minitest.rb"
386
+
387
+
388
+ Scenario: Doesn't get fucked up by RSpec autorunning
389
+ Given the file "requires_rspec_autorun.rb":
390
+ """
391
+ require 'rspec/autorun'
392
+
393
+ $load_count ||= 0
394
+ $load_count += 1
395
+
396
+ RSpec.describe 'something' do
397
+ it 'does whatever' do
398
+ $run_count ||= 0
399
+ $run_count += 1
400
+ puts "load count: #{$load_count}"
401
+ puts "run count: #{$run_count}"
402
+ end
403
+ end
404
+ """
405
+ When I run "mrspec requires_rspec_autorun.rb"
406
+ Then stdout includes "load count: 1"
407
+ And stdout includes "run count: 1"
408
+ And stdout does not include "load count: 2"
409
+ And stdout does not include "run count: 2"
410
+
411
+
412
+ Scenario: Doesn't depend on RSpec::Mocks or RSpec::Expectations
413
+ Given the file "no_dev_deps/Gemfile":
414
+ """
415
+ source 'https://rubygems.org'
416
+ gem 'mrspec', path: "{{root_dir}}"
417
+ """
418
+ When I run "env BUNDLE_GEMFILE=no_dev_deps/Gemfile bundle install"
419
+ Then the program ran successfully
420
+
421
+ Given the file "no_dev_deps/print_results.rb":
422
+ """
423
+ at_exit do
424
+ exception = $!
425
+ if exception.kind_of? SystemExit
426
+ puts "ERROR: SYSTEM EXIT (RSpec raises this if there's a failure)"
427
+ elsif exception
428
+ puts "AN ERROR: #{$!.inspect}"
429
+ else
430
+ puts "NO ERROR"
431
+ end
432
+
433
+ unexpected_deps = $LOADED_FEATURES.grep(/rspec/).grep(/mocks|expectations/)
434
+ if unexpected_deps.any?
435
+ puts "UNEXPECTED DEPS: #{unexpected_deps}"
436
+ else
437
+ puts "NO UNEXPECTED DEPS"
438
+ end
439
+ end
440
+ """
441
+ And the file "no_dev_deps/test_with_failures.rb":
442
+ """
443
+ require_relative 'print_results'
444
+ class A < Minitest::Test
445
+ def test_that_passes() assert_equal 1, 1 end
446
+ def test_that_fails() assert_equal 1, 2 end
447
+ def test_that_errors() raise "wat" end
448
+ def test_that_skips() skip end
449
+ end
450
+ """
451
+
452
+ When I run "env BUNDLE_GEMFILE=no_dev_deps/Gemfile bundle exec mrspec no_dev_deps/test_with_failures.rb"
453
+ Then stderr is empty
454
+ And stdout includes "4 examples"
455
+ And stdout includes "2 failures"
456
+ And stdout includes "1 pending"
457
+ And stdout includes "ERROR: SYSTEM EXIT"
458
+ And stdout includes "NO UNEXPECTED DEPS"
459
+
460
+ Given the file "no_dev_deps/test_that_passes.rb":
461
+ """
462
+ require_relative 'print_results'
463
+ class A < Minitest::Test
464
+ def test_that_passes() assert true end
465
+ end
466
+ """
467
+ When I run "env BUNDLE_GEMFILE=no_dev_deps/Gemfile bundle exec mrspec -f p no_dev_deps/test_that_passes.rb"
468
+ Then the program ran successfully
469
+ And stdout includes "NO ERROR"
470
+ And stdout includes "NO UNEXPECTED DEPS"
471
+
472
+ Scenario: The help screen is custom to mrspec
473
+ When I run "mrspec -h"
474
+ Then stdout includes "Usage: mrspec"
475
+ # Probably add more later
@@ -0,0 +1,31 @@
1
+ require 'haiti'
2
+ require 'tmpdir'
3
+ require 'fileutils'
4
+
5
+ proving_grounds_dir = Dir.mktmpdir
6
+ After { FileUtils.remove_entry proving_grounds_dir }
7
+
8
+ Haiti.configure do |config|
9
+ config.proving_grounds_dir = proving_grounds_dir
10
+ config.bin_dir = File.expand_path('../../../bin', __FILE__)
11
+ end
12
+
13
+ module GeneralHelpers
14
+ def pwd
15
+ Haiti.config.proving_grounds_dir
16
+ end
17
+
18
+ def root_dir
19
+ @root_dir ||= File.expand_path '../../..', __FILE__
20
+ end
21
+ end
22
+
23
+ When 'I pry' do
24
+ require "pry"
25
+ binding.pry
26
+ end
27
+
28
+ Then 'the program ran successfully' do
29
+ expect(@last_executed.stderr).to eq ""
30
+ expect(@last_executed.exitstatus).to eq 0
31
+ end
@@ -0,0 +1,18 @@
1
+ require 'mrspec/declare_minitests'
2
+ require 'rspec/core'
3
+
4
+ module MRspec
5
+ class Configuration < RSpec::Core::Configuration
6
+ def initialize(*)
7
+ super
8
+ disable_monkey_patching!
9
+ filter_gems_from_backtrace 'mrspec', 'minitest'
10
+ self.pattern = pattern.sub '*_spec.rb', '{*_spec,*_test,test_*}.rb'
11
+ end
12
+
13
+ def load_spec_files(*)
14
+ super
15
+ MRspec::DeclareMinitests.call
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,64 @@
1
+ require 'minitest'
2
+ require 'mrspec/minitest_assertion_for_rspec'
3
+
4
+ module MRspec
5
+ module DeclareMinitests
6
+ extend self
7
+
8
+ def self.call
9
+ init_minitest
10
+ wrap_classes Minitest::Runnable.runnables
11
+ end
12
+
13
+ def group_name(klass)
14
+ klass.name.sub(/^Test/, '').sub(/Test$/, '')
15
+ end
16
+
17
+ def example_name(method_name)
18
+ # remove test_, and turn underscores into spaces
19
+ # https://github.com/seattlerb/minitest/blob/f1081566ec6e9e391628bde3a26fb057ad2576a8/lib/minitest/test.rb#L62
20
+ # remove test_0001_, where the number increments
21
+ # https://github.com/seattlerb/minitest/blob/f1081566ec6e9e391628bde3a26fb057ad2576a8/lib/minitest/spec.rb#L218-222
22
+ method_name.sub(/^test_(?:\d{4}_)?/, '').tr('_', ' ')
23
+ end
24
+
25
+ def init_minitest
26
+ Minitest.reporter = Minitest::CompositeReporter.new # we're not using the reporter, but some plugins, (eg minitest/pride) expect it to be there
27
+ Minitest.load_plugins
28
+ Minitest.init_plugins Minitest.process_args([])
29
+ end
30
+
31
+ def wrap_classes(klasses)
32
+ klasses.each { |klass| wrap_class klass }
33
+ end
34
+
35
+ def wrap_class(klass)
36
+ example_group = RSpec.describe group_name(klass), klass.class_metadata
37
+ klass.runnable_methods.each do |method_name|
38
+ wrap_test example_group, klass, method_name
39
+ end
40
+ end
41
+
42
+ def wrap_test(example_group, klass, mname)
43
+ metadata = klass.example_metadata[mname.intern]
44
+ example = example_group.example example_name(mname), metadata do
45
+ instance = Minitest.run_one_method klass, mname
46
+ next if instance.passed?
47
+ pending 'skipped' if instance.skipped?
48
+ error = instance.failure.error
49
+ raise error unless error.kind_of? Minitest::Assertion
50
+ raise MinitestAssertionForRSpec.new error
51
+ end
52
+ fix_metadata example.metadata, klass.instance_method(mname)
53
+ end
54
+
55
+ def fix_metadata(metadata, method)
56
+ file, line = method.source_location
57
+ return unless file && line # not sure when this wouldn't be true, so no tests on it, but hypothetically it could happen
58
+ metadata[:file_path] = file
59
+ metadata[:line_number] = line
60
+ metadata[:location] = "#{file}:#{line}"
61
+ metadata[:absolute_file_path] = File.expand_path(file)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,15 @@
1
+ module MRspec
2
+ # With assertions, we must wrap it in a class that has "RSpec" in the name
3
+ # https://github.com/rspec/rspec-core/blob/3145e2544e1825bc754d0986e893664afe19abf5/lib/rspec/core/formatters/exception_presenter.rb#L94
4
+ # This is how RSpec differentiates failures from exceptions (errors get their class printed, failures do not)
5
+ # We could wrap it in an ExpectationNotMetError, as all their errors seem to be,
6
+ # but that is defined in rspec-expectations, which we otherwise don't depend on.
7
+ #
8
+ # We'll keep the Minitest error messages, though, as they were on par with RSpec's for all the examples I tried
9
+ class MinitestAssertionForRSpec < Exception
10
+ def initialize(assertion)
11
+ super assertion.message
12
+ set_backtrace assertion.backtrace
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ require 'minitest'
2
+
3
+ # Allow Minitest to support RSpec's metadata (eg tagging)
4
+ # Thus you can tag a test or a class, and then pass `-t mytag` to mrspec,
5
+ # and it will only run the tagged code.
6
+ class << Minitest::Runnable
7
+ # Add metadata to the current class
8
+ def classmeta(metadata)
9
+ class_metadata.merge! metadata
10
+ end
11
+
12
+ # Add metadata to the next defined test
13
+ def meta(metadata)
14
+ pending_metadata.merge! metadata
15
+ end
16
+
17
+ def class_metadata
18
+ @selfmetadata ||= {}
19
+ end
20
+
21
+ def example_metadata
22
+ @metadata ||= Hash.new { |metadata, mname| metadata[mname] = {} }
23
+ end
24
+
25
+ private
26
+
27
+ def method_added(manme)
28
+ example_metadata[manme.intern].merge! pending_metadata
29
+ pending_metadata.clear
30
+ end
31
+
32
+ def pending_metadata
33
+ @pending_metadata ||= {}
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require 'rspec/core/option_parser'
2
+
3
+ class RSpec::Core::Parser
4
+ # Trying to mitigate the invasiveness of this code.
5
+ # It's not great, but it's better than unconditionally overriding the method.
6
+ # We have to do this, b/c OptionParser will print directly to stdout/err
7
+ # and call `exit`: https://gist.github.com/JoshCheek/7adc25a46e735510558d
8
+ # The RSpec portion does this, too https://github.com/rspec/rspec-core/blob/3145e2544e1825bc754d0986e893664afe19abf5/lib/rspec/core/option_parser.rb#L267-299
9
+ # so we need to get between the definition and the parsing to make any changes
10
+
11
+ # Save RSpec's parser
12
+ alias rspec_parser parser
13
+
14
+ # Ours calls RSpec's, then modifies values on the returned parser
15
+ def mrspec_parser(*args, &b)
16
+ rspec_parser(*args, &b).tap { |parser| parser.banner.gsub! /\brspec\b/, 'mrspec' }
17
+ end
18
+
19
+ # A place to store which method `parser` actually resolves to
20
+ singleton_class.class_eval { attr_accessor :parser_method }
21
+
22
+ # Default it to RSpec's, because requiring this file shouldn't fuck up your environment,
23
+ # We'll swap the value in the binary, that decision belongs as high in the callstack as we can get it.
24
+ self.parser_method = instance_method :rspec_parser
25
+
26
+ # The actual parser method just delegates to the saved one (ultra-late binding :P)
27
+ define_method :parser do |*args, &b|
28
+ self.class.parser_method.bind(self).call(*args, &b)
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ require 'rspec/core/runner'
2
+
3
+ module MRspec
4
+ class Runner < RSpec::Core::Runner
5
+ def initialize(*)
6
+ super
7
+ # seems like there should be a better way, but I can't figure out what it is
8
+ files_and_dirs = @options.options[:files_or_directories_to_run]
9
+ files_and_dirs << 'spec' << 'test' if files_and_dirs.empty?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module MRspec
2
+ VERSION = '0.0.1'
3
+ end
data/lib/mrspec.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'mrspec/minitest_metadata'
2
+ require 'mrspec/runner'
3
+ require 'mrspec/configuration'
4
+ require 'mrspec/option_parser'
data/mrspec.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "mrspec/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "mrspec"
6
+ s.version = MRspec::VERSION
7
+ s.authors = ["Josh Cheek"]
8
+ s.email = ["josh.cheek@gmail.com"]
9
+ s.homepage = "https://github.com/JoshCheek/mrspec"
10
+ s.summary = %q{Minitest tests + RSpec's test runner}
11
+ s.description = %q{Allows you to run Minitest tests and specs with RSpec's runner, thus you can write both Minitest and RSpec, side-by-side, and take advantage of the many incredibly helpful features it supports (primarily: better formatters, --colour, --fail-fast, and tagging).}
12
+ s.license = "MIT"
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = ['mrspec']
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency "rspec-core", "~> 3.0"
20
+ s.add_dependency "minitest", "~> 5.0"
21
+
22
+ s.add_development_dependency "haiti", ">= 0.2.2", "< 0.3"
23
+ s.add_development_dependency "cucumber", "~> 2.0"
24
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mrspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Josh Cheek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: haiti
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.2
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '0.3'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 0.2.2
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.3'
61
+ - !ruby/object:Gem::Dependency
62
+ name: cucumber
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
75
+ description: 'Allows you to run Minitest tests and specs with RSpec''s runner, thus
76
+ you can write both Minitest and RSpec, side-by-side, and take advantage of the many
77
+ incredibly helpful features it supports (primarily: better formatters, --colour,
78
+ --fail-fast, and tagging).'
79
+ email:
80
+ - josh.cheek@gmail.com
81
+ executables:
82
+ - mrspec
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - ".gitignore"
87
+ - ".travis.yml"
88
+ - Gemfile
89
+ - Readme.md
90
+ - bin/mrspec
91
+ - features/mrspec.feature
92
+ - features/support/env.rb
93
+ - lib/mrspec.rb
94
+ - lib/mrspec/configuration.rb
95
+ - lib/mrspec/declare_minitests.rb
96
+ - lib/mrspec/minitest_assertion_for_rspec.rb
97
+ - lib/mrspec/minitest_metadata.rb
98
+ - lib/mrspec/option_parser.rb
99
+ - lib/mrspec/runner.rb
100
+ - lib/mrspec/version.rb
101
+ - mrspec.gemspec
102
+ homepage: https://github.com/JoshCheek/mrspec
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.4.1
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Minitest tests + RSpec's test runner
126
+ test_files:
127
+ - features/mrspec.feature
128
+ - features/support/env.rb
129
+ has_rdoc: