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.
- checksums.yaml +5 -5
- data/.travis.yml +3 -2
- data/CHANGELOG.md +19 -0
- data/README.md +3 -0
- data/lib/minitest_to_rspec/converter.rb +2 -2
- data/lib/minitest_to_rspec/input/model/base.rb +15 -0
- data/lib/minitest_to_rspec/input/model/call.rb +185 -0
- data/lib/minitest_to_rspec/input/model/defn.rb +37 -0
- data/lib/minitest_to_rspec/input/model/hash_exp.rb +24 -0
- data/lib/minitest_to_rspec/input/model/iter.rb +89 -0
- data/lib/minitest_to_rspec/input/model/klass.rb +102 -0
- data/lib/minitest_to_rspec/input/processor.rb +40 -0
- data/lib/minitest_to_rspec/input/subprocessors/base.rb +91 -0
- data/lib/minitest_to_rspec/input/subprocessors/call.rb +279 -0
- data/lib/minitest_to_rspec/input/subprocessors/defn.rb +64 -0
- data/lib/minitest_to_rspec/input/subprocessors/iter.rb +148 -0
- data/lib/minitest_to_rspec/input/subprocessors/klass.rb +101 -0
- data/lib/minitest_to_rspec/minitest/stub.rb +85 -0
- data/lib/minitest_to_rspec/{expression_builders → rspec}/stub.rb +3 -2
- data/lib/minitest_to_rspec/type.rb +1 -1
- data/lib/minitest_to_rspec/version.rb +1 -1
- metadata +17 -18
- data/lib/minitest_to_rspec/model/base.rb +0 -13
- data/lib/minitest_to_rspec/model/call.rb +0 -165
- data/lib/minitest_to_rspec/model/calls/once.rb +0 -13
- data/lib/minitest_to_rspec/model/calls/twice.rb +0 -13
- data/lib/minitest_to_rspec/model/defn.rb +0 -27
- data/lib/minitest_to_rspec/model/hash_exp.rb +0 -20
- data/lib/minitest_to_rspec/model/iter.rb +0 -79
- data/lib/minitest_to_rspec/model/klass.rb +0 -100
- data/lib/minitest_to_rspec/processor.rb +0 -38
- data/lib/minitest_to_rspec/subprocessors/base.rb +0 -89
- data/lib/minitest_to_rspec/subprocessors/call.rb +0 -391
- data/lib/minitest_to_rspec/subprocessors/defn.rb +0 -49
- data/lib/minitest_to_rspec/subprocessors/iter.rb +0 -138
- 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
|