minitest_to_rspec 0.11.0 → 0.12.0

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