minitest_to_rspec 0.11.0 → 0.12.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.
Files changed (36) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +3 -2
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +3 -0
  5. data/lib/minitest_to_rspec/converter.rb +2 -2
  6. data/lib/minitest_to_rspec/input/model/base.rb +15 -0
  7. data/lib/minitest_to_rspec/input/model/call.rb +185 -0
  8. data/lib/minitest_to_rspec/input/model/defn.rb +37 -0
  9. data/lib/minitest_to_rspec/input/model/hash_exp.rb +24 -0
  10. data/lib/minitest_to_rspec/input/model/iter.rb +89 -0
  11. data/lib/minitest_to_rspec/input/model/klass.rb +102 -0
  12. data/lib/minitest_to_rspec/input/processor.rb +40 -0
  13. data/lib/minitest_to_rspec/input/subprocessors/base.rb +91 -0
  14. data/lib/minitest_to_rspec/input/subprocessors/call.rb +279 -0
  15. data/lib/minitest_to_rspec/input/subprocessors/defn.rb +64 -0
  16. data/lib/minitest_to_rspec/input/subprocessors/iter.rb +148 -0
  17. data/lib/minitest_to_rspec/input/subprocessors/klass.rb +101 -0
  18. data/lib/minitest_to_rspec/minitest/stub.rb +85 -0
  19. data/lib/minitest_to_rspec/{expression_builders → rspec}/stub.rb +3 -2
  20. data/lib/minitest_to_rspec/type.rb +1 -1
  21. data/lib/minitest_to_rspec/version.rb +1 -1
  22. metadata +17 -18
  23. data/lib/minitest_to_rspec/model/base.rb +0 -13
  24. data/lib/minitest_to_rspec/model/call.rb +0 -165
  25. data/lib/minitest_to_rspec/model/calls/once.rb +0 -13
  26. data/lib/minitest_to_rspec/model/calls/twice.rb +0 -13
  27. data/lib/minitest_to_rspec/model/defn.rb +0 -27
  28. data/lib/minitest_to_rspec/model/hash_exp.rb +0 -20
  29. data/lib/minitest_to_rspec/model/iter.rb +0 -79
  30. data/lib/minitest_to_rspec/model/klass.rb +0 -100
  31. data/lib/minitest_to_rspec/processor.rb +0 -38
  32. data/lib/minitest_to_rspec/subprocessors/base.rb +0 -89
  33. data/lib/minitest_to_rspec/subprocessors/call.rb +0 -391
  34. data/lib/minitest_to_rspec/subprocessors/defn.rb +0 -49
  35. data/lib/minitest_to_rspec/subprocessors/iter.rb +0 -138
  36. data/lib/minitest_to_rspec/subprocessors/klass.rb +0 -99
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_parser'
4
+ require 'sexp_processor'
5
+ require_relative 'subprocessors/call'
6
+ require_relative 'subprocessors/defn'
7
+ require_relative 'subprocessors/klass'
8
+ require_relative 'subprocessors/iter'
9
+
10
+ module MinitestToRspec
11
+ module Input
12
+ # Consumes a `String` of minitest code and returns an S-expression
13
+ # representing equivalent RSpec code. The main method is `#process`. See
14
+ # `SexpProcessor` docs for details.
15
+ class Processor < SexpProcessor
16
+ def initialize(rails, mocha)
17
+ super()
18
+ self.strict = false
19
+ @mocha = mocha
20
+ @rails = rails
21
+ end
22
+
23
+ def process_call(exp)
24
+ Subprocessors::Call.new(exp, @rails, @mocha).process
25
+ end
26
+
27
+ def process_class(exp)
28
+ Subprocessors::Klass.new(exp, @rails, @mocha).process
29
+ end
30
+
31
+ def process_defn(exp)
32
+ Subprocessors::Defn.new(exp, @rails, @mocha).process
33
+ end
34
+
35
+ def process_iter(exp)
36
+ Subprocessors::Iter.new(exp, @rails, @mocha).process
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_to_rspec/sexp_assertions'
4
+
5
+ module MinitestToRspec
6
+ module Input
7
+ module Subprocessors
8
+ # Parent class of "sub-processors". There is one sub-processor for each
9
+ # `sexp_type` that `Processor` knows how to process.
10
+ #
11
+ # For example, `Subprocessors::Call` will process an `s(:call, ..)`
12
+ # expression representing minitest code, and return an S-expression
13
+ # representing equivalent RSpec code.
14
+ class Base
15
+ include SexpAssertions
16
+
17
+ def initialize(rails, mocha)
18
+ @rails = rails
19
+ @mocha = mocha
20
+ end
21
+
22
+ # Returns a s-expression representing an rspec-mocks stub.
23
+ def allow_to(msg_recipient, matcher, any_instance = false)
24
+ allow_method = any_instance ? :allow_any_instance_of : :allow
25
+ target = s(:call, nil, allow_method, msg_recipient)
26
+ s(:call, target, :to, matcher)
27
+ end
28
+
29
+ # Returns a s-expression representing an RSpec expectation, i.e. the
30
+ # combination of an "expectation target" and a matcher.
31
+ def expect(target, eager, phase, matcher, any_instance)
32
+ et = expectation_target(target, eager, any_instance)
33
+ s(:call, et, phase, matcher)
34
+ end
35
+
36
+ def expect_to(matcher, target, eager, any_instance = false)
37
+ expect(target, eager, :to, matcher, any_instance)
38
+ end
39
+
40
+ def expect_to_not(matcher, target, eager)
41
+ expect(target, eager, :to_not, matcher, false)
42
+ end
43
+
44
+ # In RSpec, `expect` returns an "expectation target". This
45
+ # can be based on an expression, as in `expect(1 + 1)` or it
46
+ # can be based on a block, as in `expect { raise }`. Either
47
+ # way, it's called an "expectation target".
48
+ def expectation_target(exp, eager, any_instance)
49
+ if eager
50
+ expectation_target_eager(exp, any_instance)
51
+ else
52
+ expectation_target_lazy(exp)
53
+ end
54
+ end
55
+
56
+ def expectation_target_eager(exp, any_instance)
57
+ expect_method = any_instance ? :expect_any_instance_of : :expect
58
+ s(:call, nil, expect_method, exp)
59
+ end
60
+
61
+ def expectation_target_lazy(block)
62
+ s(:iter,
63
+ s(:call, nil, :expect),
64
+ 0,
65
+ full_process(block)
66
+ )
67
+ end
68
+
69
+ # If it's a `Sexp`, run `obj` through a new `Processor`. Otherwise,
70
+ # return `obj`.
71
+ #
72
+ # This is useful for expressions that cannot be fully understood by a
73
+ # single subprocessor. For example, we must begin processing all :iter
74
+ # expressions, because some :iter represent calls we're interested in,
75
+ # e.g. `assert_difference`. However, if the :iter turns out to be
76
+ # uninteresting (perhaps it has no assertions) we still want to fully
77
+ # process its sub-expressions.
78
+ #
79
+ # TODO: `full_process` may not be the best name.
80
+ def full_process(obj)
81
+ obj.is_a?(Sexp) ? Processor.new(@rails, @mocha).process(obj) : obj
82
+ end
83
+
84
+ def matcher(name, *args)
85
+ exp = s(:call, nil, name)
86
+ exp.concat(args)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_to_rspec/errors'
4
+ require 'minitest_to_rspec/input/model/call'
5
+ require 'minitest_to_rspec/input/model/hash_exp'
6
+ require 'minitest_to_rspec/input/subprocessors/base'
7
+ require 'minitest_to_rspec/minitest/stub'
8
+ require 'minitest_to_rspec/rspec/stub'
9
+ require 'minitest_to_rspec/type'
10
+
11
+ module MinitestToRspec
12
+ module Input
13
+ module Subprocessors
14
+ # Processes `s(:call, ..)` expressions.
15
+ class Call < Base
16
+ # Mocha methods will only be processed if `--mocha` flag was given,
17
+ # i.e. `mocha` argument in constructor is true.
18
+ MOCHA_METHODS = %i[
19
+ expects
20
+ once
21
+ returns
22
+ stub
23
+ stubs
24
+ stub_everything
25
+ twice
26
+ ].freeze
27
+
28
+ def initialize(sexp, rails, mocha)
29
+ super(rails, mocha)
30
+ @exp = Model::Call.new(sexp)
31
+ sexp.clear
32
+ end
33
+
34
+ # Given a `Model::Call`, returns a `Sexp`
35
+ def process
36
+ if process?
37
+ send(name_of_processing_method)
38
+ else
39
+ @exp.original
40
+ end
41
+ end
42
+
43
+ def process?
44
+ respond_to?(name_of_processing_method, true) &&
45
+ (@mocha || !MOCHA_METHODS.include?(@exp.method_name))
46
+ end
47
+
48
+ private
49
+
50
+ def be_falsey
51
+ matcher(:be_falsey)
52
+ end
53
+
54
+ def be_nil
55
+ matcher(:be_nil)
56
+ end
57
+
58
+ def be_truthy
59
+ matcher(:be_truthy)
60
+ end
61
+
62
+ def call_to_question_mark?(exp)
63
+ sexp_type?(:call, exp) && Model::Call.new(exp).question_mark_method?
64
+ end
65
+
66
+ def eq(exp)
67
+ matcher(:eq, exp)
68
+ end
69
+
70
+ def match(pattern)
71
+ matcher(:match, pattern)
72
+ end
73
+
74
+ def method_assert
75
+ refsert eq(s(:true)), be_truthy
76
+ end
77
+
78
+ def method_assert_equal
79
+ expected = @exp.arguments[0]
80
+ calculated = @exp.arguments[1]
81
+ expect_to(eq(expected), calculated, true)
82
+ end
83
+
84
+ def method_assert_match
85
+ pattern = @exp.arguments[0]
86
+ string = @exp.arguments[1]
87
+ expect_to(match(pattern), string, true)
88
+ end
89
+
90
+ def method_assert_nil
91
+ expect_to(be_nil, @exp.arguments[0], true)
92
+ end
93
+
94
+ def method_assert_not_nil
95
+ expect_to_not(be_nil, @exp.arguments[0], true)
96
+ end
97
+
98
+ def method_assert_not_equal
99
+ expected = @exp.arguments[0]
100
+ calculated = @exp.arguments[1]
101
+ expect_to_not(eq(expected), calculated, true)
102
+ end
103
+
104
+ def method_expects
105
+ if @exp.num_arguments == 1 &&
106
+ %i[lit hash].include?(@exp.arguments.first.sexp_type)
107
+ mocha_stub
108
+ else
109
+ @exp.original
110
+ end
111
+ end
112
+
113
+ def method_once
114
+ mocha_stub
115
+ end
116
+
117
+ def method_refute
118
+ refsert eq(s(:false)), be_falsey
119
+ end
120
+
121
+ def method_refute_equal
122
+ unexpected = @exp.arguments[0]
123
+ calculated = @exp.arguments[1]
124
+ expect_to_not(eq(unexpected), calculated, true)
125
+ end
126
+
127
+ # Processes an entire line of code that ends in `.returns`
128
+ def method_returns
129
+ if @exp.num_arguments.zero?
130
+ @exp.original
131
+ else
132
+ mocha_stub
133
+ end
134
+ end
135
+
136
+ def method_require
137
+ if @exp.require_test_helper?
138
+ require_spec_helper
139
+ else
140
+ @exp.original
141
+ end
142
+ end
143
+
144
+ def method_should
145
+ s(:call, nil, :it, *@exp.arguments)
146
+ end
147
+
148
+ # Happily, the no-block signatures of [stub][3] are the
149
+ # same as [double][2].
150
+ #
151
+ # - (name)
152
+ # - (stubs)
153
+ # - (name, stubs)
154
+ def method_stub
155
+ raise ArgumentError unless @exp.is_a?(Model::Call)
156
+ if @exp.receiver.nil?
157
+ s(:call, nil, :double, *@exp.arguments)
158
+ else
159
+ @exp.original
160
+ end
161
+ end
162
+
163
+ # [stub_everything][1] responds to all messages with nil.
164
+ # [double.as_null_object][4] responds with self. Not a
165
+ # drop-in replacement, but will work in many situations.
166
+ # RSpec doesn't provide an equivalent to `stub_everything`,
167
+ # AFAIK.
168
+ def method_stub_everything
169
+ if @exp.receiver.nil?
170
+ d = s(:call, nil, :double, *@exp.arguments)
171
+ s(:call, d, :as_null_object)
172
+ else
173
+ @exp.original
174
+ end
175
+ end
176
+
177
+ def method_test
178
+ s(:call, nil, :it, *@exp.arguments)
179
+ end
180
+
181
+ def method_twice
182
+ mocha_stub
183
+ end
184
+
185
+ # Given a sexp representing the hash from a mocha shorthand stub, as in
186
+ # `Banana.expects(edible: true, color: "yellow")`
187
+ # return an array of separate RSpec stubs, one for each hash key.
188
+ def mocha_shorthand_stub_to_rspec_stubs(shorthand_stub_hash, mt_stub)
189
+ Model::HashExp.new(shorthand_stub_hash).to_h.map { |k, v|
190
+ Rspec::Stub.new(
191
+ mt_stub.receiver,
192
+ mt_stub.any_instance?,
193
+ k,
194
+ mt_stub.with,
195
+ v,
196
+ 1
197
+ ).to_rspec_exp
198
+ }
199
+ end
200
+
201
+ def mocha_stub
202
+ mt_stub = Minitest::Stub.new(@exp)
203
+ msg = mt_stub.message
204
+ if sexp_type?(:hash, msg)
205
+ pointless_lambda(mocha_shorthand_stub_to_rspec_stubs(msg, mt_stub))
206
+ else
207
+ Rspec::Stub.new(
208
+ mt_stub.receiver,
209
+ mt_stub.any_instance?,
210
+ mt_stub.message,
211
+ mt_stub.with,
212
+ mt_stub.returns,
213
+ mt_stub.count
214
+ ).to_rspec_exp
215
+ end
216
+ rescue UnknownVariant
217
+ @exp.original
218
+ end
219
+
220
+ def name_of_processing_method
221
+ "method_#{@exp.method_name}".to_sym
222
+ end
223
+
224
+ # Given `array_of_calls`, returns a `Sexp` representing a
225
+ # self-executing lambda.
226
+ #
227
+ # This works around the fact that `sexp_processor` expects us to return
228
+ # a single `Sexp`, not an array of `Sexp`. We also can't return a
229
+ # `:block`, or else certain input would produce nested blocks (e.g.
230
+ # `s(:block, s(:block, ..))`) which `ruby2ruby` (naturally) does not
231
+ # know how to process. So, the easiest solution I could think of is a
232
+ # self-executing lambda.
233
+ #
234
+ # Currently, the only `:call` which we process into multiple calls is
235
+ # the hash form of a mocha `#expects`, thankfully uncommon.
236
+ #
237
+ # To get better output (without a pointless lambda) we would have to
238
+ # process `:block` *and* `:defn`, which we are not yet doing.
239
+ def pointless_lambda(array_of_calls)
240
+ assert_sexp_type_array(:call, array_of_calls)
241
+ s(:call,
242
+ s(:iter,
243
+ s(:call, nil, :lambda),
244
+ 0,
245
+ s(:block,
246
+ s(:str, 'Sorry for the pointless lambda here.'),
247
+ *array_of_calls
248
+ )
249
+ ),
250
+ :call
251
+ )
252
+ end
253
+
254
+ # `refsert` - Code shared by refute and assert. I could also have gone
255
+ # with `assfute`. Wooo .. time for bed.
256
+ def refsert(exact, fuzzy)
257
+ actual = @exp.arguments[0]
258
+ matcher = call_to_question_mark?(actual) ? exact : fuzzy
259
+ expect_to(matcher, actual, true)
260
+ end
261
+
262
+ def require_spec_helper
263
+ prefix = @rails ? 'rails' : 'spec'
264
+ s(:call, nil, :require, s(:str, "#{prefix}_helper"))
265
+ end
266
+
267
+ # Wraps `obj` in an `Array` if it is a `Sexp`
268
+ def wrap_sexp(obj)
269
+ obj.is_a?(Sexp) ? [obj] : obj
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ # [1]: http://bit.ly/1yll6ND
277
+ # [2]: http://bit.ly/1CRdmP3
278
+ # [3]: http://bit.ly/1aY2mJN
279
+ # [4]: http://bit.ly/1OtwDOY
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_to_rspec/input/processor'
4
+ require 'minitest_to_rspec/input/subprocessors/base'
5
+ require 'minitest_to_rspec/input/model/defn'
6
+
7
+ module MinitestToRspec
8
+ module Input
9
+ module Subprocessors
10
+ # Minitest tests can be defined as methods using names beginning with
11
+ # 'test_'. Process those tests into RSpec `it` example blocks.
12
+ class Defn < Base
13
+ def initialize(sexp, rails, mocha)
14
+ super(rails, mocha)
15
+ @original = sexp.dup
16
+ @exp = Model::Defn.new(sexp)
17
+ sexp.clear
18
+ end
19
+
20
+ # Using a `Model::Defn`, returns a `Sexp`
21
+ def process
22
+ if @exp.test_method?
23
+ s(:iter,
24
+ s(:call, nil, :it, s(:str, example_title)),
25
+ 0,
26
+ example_block)
27
+ elsif @exp.setup?
28
+ s(:iter,
29
+ s(:call, nil, :before),
30
+ 0,
31
+ example_block
32
+ )
33
+ elsif @exp.teardown?
34
+ s(:iter,
35
+ s(:call, nil, :after),
36
+ 0,
37
+ example_block
38
+ )
39
+ else
40
+ @original
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Remove 'test_' prefix and replace underscores with spaces
47
+ def example_title
48
+ @exp.method_name.sub(/^test_/, '').tr('_', ' ')
49
+ end
50
+
51
+ def example_block
52
+ block = s(:block)
53
+ @exp.body.each_with_object(block) do |line, blk|
54
+ blk << process_line(line)
55
+ end
56
+ end
57
+
58
+ def process_line(line)
59
+ Processor.new(@rails, @mocha).process(line)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end