mrspec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: