minitestify 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b8d0d4202c4c1c1aba785fcea7db1810ee21e58a1f6f904b0d95f2f8199490a
4
- data.tar.gz: 0a9a0126d2f41ec3b688fd87562850a301bd12515eed86c0589530f41feded07
3
+ metadata.gz: b169c65b41f33533e17a58e0cd66140a64d08039f6e20df0679d5746ac4ff99b
4
+ data.tar.gz: 6207f274d48bf806f3dcf2f490ffe1cc44d4aa0a4a36b521b47b8caaa8af01f8
5
5
  SHA512:
6
- metadata.gz: '080b030988c144384db8b694691acf0ece4dfbf013897753850fd8a1eea006016653b0726577e302f6b3796ca968c704c8a293258462b4a39a55d27060cd0d22'
7
- data.tar.gz: 040341ca4fb0a102b8545d66bc5254054e16341c7c71e77933b50c957a916b12fca8fb0fec49ec002ade98bcbe5586ef05736b623ce8c7d2d095345d0d5bf052
6
+ metadata.gz: 0af88789ce5f56b3170cbb5fdb4fad7a68f890e09bc1f6f2748466e5c25734d5a0b8d6a02b344fbc492fc229be6392677c37ffd1e77a9d41093d5cd990fe38e2
7
+ data.tar.gz: ad16675adb29636e70805c58cde16a43871588f5e1171dbe585a2cec43caf6bf9a54ae8c6ff9944630906091b5be5dce8cbd23fe23f014f146b2460ab0898ab9
data/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # Minitestify
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/minitestify`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ minitestify is a tool to convert Rspec specs to equivalent minitest tests by parsing and transforming the code using the [syntax_tree](https://github.com/ruby-syntax-tree/syntax_tree) gem.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ It's early days and this work is still experimental. Here's what it can do now
6
+
7
+ ### Capabilities
8
+ - describe -> class
9
+ - it -> def test_*
10
+ - expect(actual).to eq(expected) -> assert_equal(expected, actual)
6
11
 
7
12
  ## Installation
8
13
 
@@ -16,7 +21,9 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
21
 
17
22
  ## Usage
18
23
 
19
- TODO: Write usage instructions here
24
+ ```
25
+ minitestify print FILES # Convert one or more specs to minitest and print to standard out
26
+ ```
20
27
 
21
28
  ## Development
22
29
 
data/exe/minitestify ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "dry/cli"
6
+ require "minitestify/cli"
7
+
8
+ Dry::CLI.new(Minitestify::CLI::Commands).call
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli"
4
+ require "minitestify"
5
+ require "minitestify/version"
6
+ require "minitestify/spec"
7
+
8
+ module Minitestify::CLI
9
+ module Commands
10
+ extend Dry::CLI::Registry
11
+
12
+ class Version < Dry::CLI::Command
13
+ desc "Print version"
14
+
15
+ def call(*)
16
+ puts Minitestify::VERSION
17
+ end
18
+ end
19
+
20
+ class Print < Dry::CLI::Command
21
+ desc "Convert one or more specs to minitest and print to standard out"
22
+
23
+ argument :files, type: :array, required: true, desc: "Spec files to convert"
24
+
25
+ example [
26
+ "spec/dog_spec.rb # Generates dog_test.rb and prints to standard out"
27
+ ]
28
+
29
+ def call(files:, **)
30
+ files.each do |file|
31
+ spec = Minitestify::Spec.new(file: file)
32
+ puts "# #{spec.to_test_filepath}"
33
+ puts spec.to_test_code
34
+ puts
35
+ end
36
+ end
37
+ end
38
+
39
+ register "version", Version, aliases: ["v", "-v", "--version"]
40
+ register "print", Print, aliases: ["p", "-p", "--print"]
41
+ end
42
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitestify"
4
+
5
+ module Minitestify
6
+ class Spec
7
+ def initialize(file:)
8
+ @file = file
9
+ end
10
+
11
+ def to_test_filepath
12
+ @file.gsub "spec", "test"
13
+ # TODO be smarter here and dissect the file path
14
+ # replacing only the spec dir (if present)
15
+ # and the _spec.rb suffix
16
+ end
17
+
18
+ def to_test_code
19
+ source = SyntaxTree.read(@file)
20
+ program = SyntaxTree.parse(source)
21
+
22
+ visitor = SyntaxTree::Visitor::MutationVisitor.new
23
+ inflector = Dry::Inflector.new
24
+
25
+ # describe -> class
26
+ visitor.mutate("Command[ message: Ident[value: \"describe\"] ]") do |node|
27
+ node => SyntaxTree::Command[
28
+
29
+ message: SyntaxTree::Ident[value: "describe"],
30
+ arguments: SyntaxTree::Args[
31
+ parts: [SyntaxTree::VarRef[value: SyntaxTree::Const[value: value]]]
32
+ ]
33
+ ]
34
+
35
+ SyntaxTree::ClassDeclaration.new(
36
+ constant:
37
+ SyntaxTree::ConstRef.new(
38
+ constant:
39
+ SyntaxTree::Const.new(
40
+ value: "#{inflector.camelize_upper(value)}Test",
41
+ location: node.location
42
+ ),
43
+ location: node.location
44
+ ),
45
+ superclass:
46
+ SyntaxTree::ConstPathRef.new(
47
+ parent:
48
+ SyntaxTree::VarRef.new(
49
+ value:
50
+ SyntaxTree::Const.new(
51
+ value: "Minitest",
52
+ location: node.location
53
+ ),
54
+ location: node.location
55
+ ),
56
+ constant:
57
+ SyntaxTree::Const.new(value: "Test", location: node.location),
58
+ location: node.location
59
+ ),
60
+ bodystmt: node.child_nodes.last.bodystmt,
61
+ location: node.location
62
+ )
63
+ end
64
+
65
+ # it -> def
66
+ visitor.mutate("Command[message: Ident[value: \"it\"]]") do |node|
67
+ node => SyntaxTree::Command[
68
+ message: SyntaxTree::Ident[value: "it"],
69
+ arguments: SyntaxTree::Args[
70
+ parts: [
71
+ SyntaxTree::StringLiteral[
72
+ parts: [SyntaxTree::TStringContent[value: value]]
73
+ ]
74
+ ]
75
+ ]
76
+ ]
77
+
78
+ SyntaxTree::DefNode.new(
79
+ target: nil,
80
+ operator: nil,
81
+ name:
82
+ SyntaxTree::Ident.new(
83
+ value: "test_#{value.gsub(" ", "_")}",
84
+ location: node.location
85
+ ),
86
+ params: SyntaxTree::Params,
87
+ bodystmt: node.child_nodes.last.bodystmt,
88
+ location: node.location
89
+ )
90
+ end
91
+
92
+ expect_eq_search =
93
+ 'CommandCall[
94
+ receiver: CallNode[
95
+ message: Ident[value: "expect"]
96
+ ],
97
+ operator: Period[value: "."],
98
+ message: Ident[value: "to"],
99
+ arguments: Args[
100
+ parts: [ CallNode[
101
+ message: Ident[value: "eq"]
102
+ ]
103
+ ]
104
+ ]
105
+ ]'
106
+ visitor.mutate(expect_eq_search) do |node|
107
+ node => SyntaxTree::CommandCall[
108
+ receiver: SyntaxTree::CallNode[
109
+ receiver: nil,
110
+ operator: nil,
111
+ message: SyntaxTree::Ident[value: "expect"],
112
+ arguments: SyntaxTree::ArgParen[
113
+ arguments: SyntaxTree::Args[parts: [actual_expr]]
114
+ ]
115
+ ],
116
+ operator: SyntaxTree::Period[value: "."],
117
+ message: SyntaxTree::Ident[value: "to"],
118
+ arguments: SyntaxTree::Args[
119
+ parts: [
120
+ SyntaxTree::CallNode[
121
+ receiver: nil,
122
+ operator: nil,
123
+ message: SyntaxTree::Ident[value: "eq"],
124
+ arguments: SyntaxTree::ArgParen[
125
+ arguments: SyntaxTree::Args[parts: [expected_expr]]
126
+ ]
127
+ ]
128
+ ]
129
+ ]
130
+ ]
131
+
132
+ SyntaxTree::CallNode.new(
133
+ message:
134
+ SyntaxTree::Ident.new(
135
+ value: "assert_equal",
136
+ location: node.location
137
+ ),
138
+ arguments:
139
+ SyntaxTree::ArgParen.new(
140
+ arguments:
141
+ SyntaxTree::Args.new(
142
+ parts: [expected_expr, actual_expr],
143
+ location: node.location
144
+ ),
145
+ location: node.location
146
+ ),
147
+ location: node.location,
148
+ receiver: nil,
149
+ operator: nil
150
+ )
151
+ end
152
+
153
+ SyntaxTree::Formatter.format(source, program.accept(visitor))
154
+ end
155
+ end
156
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minitestify
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/minitestify.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "minitestify/version"
4
+ require "syntax_tree"
5
+ require "dry/inflector"
4
6
 
5
7
  module Minitestify
6
- class Error < StandardError; end
7
- # Your code goes here...
8
+ class Error < StandardError
9
+ end
8
10
  end
data/minitestify.gemspec CHANGED
@@ -30,6 +30,8 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  # Uncomment to register a new dependency of your gem
32
32
  spec.add_dependency "syntax_tree", "~> 5.0"
33
+ spec.add_dependency "dry-inflector", "~> 1.0"
34
+ spec.add_dependency "dry-cli", "~> 1.0"
33
35
 
34
36
  # For more information and examples about making a new gem, check out our
35
37
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -0,0 +1,237 @@
1
+ # How to add a transformation
2
+
3
+ Run the following in an irb session to determine the search pattern
4
+
5
+ ```ruby
6
+ require "syntax_tree"
7
+
8
+ code = <<~RUBY
9
+ expect(calculator.add(1, 1)).to eq(2)
10
+ RUBY
11
+
12
+ program = SyntaxTree.parse code
13
+ puts program.construct_keys
14
+ ```
15
+
16
+ You'll get a bunch of output like this
17
+ ```ruby
18
+ SyntaxTree::Program[
19
+ statements: SyntaxTree::Statements[
20
+ body: [
21
+ SyntaxTree::CommandCall[
22
+ receiver: SyntaxTree::CallNode[
23
+ receiver: nil,
24
+ operator: nil,
25
+ message: SyntaxTree::Ident[value: "expect"],
26
+ arguments: SyntaxTree::ArgParen[
27
+ arguments: SyntaxTree::Args[
28
+ parts: [
29
+ SyntaxTree::CallNode[
30
+ receiver: SyntaxTree::VCall[
31
+ value: SyntaxTree::Ident[value: "calculator"]
32
+ ],
33
+ operator: SyntaxTree::Period[value: "."],
34
+ message: SyntaxTree::Ident[value: "add"],
35
+ arguments: SyntaxTree::ArgParen[
36
+ arguments: SyntaxTree::Args[
37
+ parts: [
38
+ SyntaxTree::Int[value: "1"],
39
+ SyntaxTree::Int[value: "1"]
40
+ ]
41
+ ]
42
+ ]
43
+ ]
44
+ ]
45
+ ]
46
+ ]
47
+ ],
48
+ operator: SyntaxTree::Period[value: "."],
49
+ message: SyntaxTree::Ident[value: "to"],
50
+ arguments: SyntaxTree::Args[
51
+ parts: [
52
+ SyntaxTree::CallNode[
53
+ receiver: nil,
54
+ operator: nil,
55
+ message: SyntaxTree::Ident[value: "eq"],
56
+ arguments: SyntaxTree::ArgParen[
57
+ arguments: SyntaxTree::Args[
58
+ parts: [SyntaxTree::Int[value: "2"]]
59
+ ]
60
+ ]
61
+ ]
62
+ ]
63
+ ]
64
+ ]
65
+ ]
66
+ ]
67
+ ]
68
+ ```
69
+
70
+ We want the top level node of the body. Remove the SyntaxTree namespaces and take just enough to identify the top node
71
+ ```ruby
72
+ expect_eq_search =
73
+ 'CommandCall[
74
+ receiver: CallNode[
75
+ message: Ident[value: "expect"]
76
+ ],
77
+ operator: Period[value: "."],
78
+ message: Ident[value: "to"],
79
+ arguments: Args[
80
+ parts: [ CallNode[
81
+ message: Ident[value: "eq"]
82
+ ]
83
+ ]
84
+ ]
85
+ ]'
86
+ ```
87
+
88
+ And use that as our argument to `visitor.mutate`
89
+ ```ruby
90
+ visitor.mutate(expect_eq_search) do
91
+ # ...
92
+ end
93
+ ```
94
+
95
+ For the block body, start by pulling out relevant information with a pattern matching statement from construct_keys
96
+ TODO pare down the pattern match and extract variables
97
+ ```ruby
98
+ node => SyntaxTree::CommandCall[
99
+ receiver: SyntaxTree::CallNode[
100
+ receiver: nil,
101
+ operator: nil,
102
+ message: SyntaxTree::Ident[value: "expect"],
103
+ arguments: SyntaxTree::ArgParen[
104
+ arguments: SyntaxTree::Args[parts: [actual_expr]]
105
+ ]
106
+ ],
107
+ operator: SyntaxTree::Period[value: "."],
108
+ message: SyntaxTree::Ident[value: "to"],
109
+ arguments: SyntaxTree::Args[
110
+ parts: [
111
+ SyntaxTree::CallNode[
112
+ receiver: nil,
113
+ operator: nil,
114
+ message: SyntaxTree::Ident[value: "eq"],
115
+ arguments: SyntaxTree::ArgParen[
116
+ arguments: SyntaxTree::Args[parts: [expected_expr]]
117
+ ]
118
+ ]
119
+ ]
120
+ ]
121
+ ]
122
+ ```
123
+
124
+ Now write the code you want to transform it to and construct the nodes
125
+
126
+ ```ruby
127
+ require "syntax_tree"
128
+
129
+ code = <<~RUBY
130
+ assert_equal(2, calculator.add(1, 1))
131
+ RUBY
132
+
133
+ program = SyntaxTree.parse code
134
+ puts program.construct_keys
135
+ ```
136
+
137
+ Again you get a bunch of output
138
+ ```ruby
139
+ SyntaxTree::Program[
140
+ statements: SyntaxTree::Statements[
141
+ body: [
142
+ SyntaxTree::CallNode[
143
+ receiver: nil,
144
+ operator: nil,
145
+ message: SyntaxTree::Ident[value: "assert_equal"],
146
+ arguments: SyntaxTree::ArgParen[
147
+ arguments: SyntaxTree::Args[
148
+ parts: [
149
+ SyntaxTree::Int[value: "2"],
150
+ SyntaxTree::CallNode[
151
+ receiver: SyntaxTree::VCall[
152
+ value: SyntaxTree::Ident[value: "calculator"]
153
+ ],
154
+ operator: SyntaxTree::Period[value: "."],
155
+ message: SyntaxTree::Ident[value: "add"],
156
+ arguments: SyntaxTree::ArgParen[
157
+ arguments: SyntaxTree::Args[
158
+ parts: [
159
+ SyntaxTree::Int[value: "1"],
160
+ SyntaxTree::Int[value: "1"]
161
+ ]
162
+ ]
163
+ ]
164
+ ]
165
+ ]
166
+ ]
167
+ ]
168
+ ]
169
+ ]
170
+ ]
171
+ ]
172
+ ```
173
+
174
+ Add `.new` and convert parentheses. You'll have to add `location: node.location` where required (trial and error). Make this node tree the return value of the mutate block.
175
+
176
+ ```ruby
177
+ expect_eq_search =
178
+ 'CommandCall[
179
+ receiver: CallNode[
180
+ message: Ident[value: "expect"]
181
+ ],
182
+ operator: Period[value: "."],
183
+ message: Ident[value: "to"],
184
+ arguments: Args[
185
+ parts: [ CallNode[
186
+ message: Ident[value: "eq"]
187
+ ]
188
+ ]
189
+ ]
190
+ ]'
191
+ visitor.mutate(expect_eq_search) do |node|
192
+ node => SyntaxTree::CommandCall[
193
+ receiver: SyntaxTree::CallNode[
194
+ receiver: nil,
195
+ operator: nil,
196
+ message: SyntaxTree::Ident[value: "expect"],
197
+ arguments: SyntaxTree::ArgParen[
198
+ arguments: SyntaxTree::Args[parts: [actual_expr]]
199
+ ]
200
+ ],
201
+ operator: SyntaxTree::Period[value: "."],
202
+ message: SyntaxTree::Ident[value: "to"],
203
+ arguments: SyntaxTree::Args[
204
+ parts: [
205
+ SyntaxTree::CallNode[
206
+ receiver: nil,
207
+ operator: nil,
208
+ message: SyntaxTree::Ident[value: "eq"],
209
+ arguments: SyntaxTree::ArgParen[
210
+ arguments: SyntaxTree::Args[parts: [expected_expr]]
211
+ ]
212
+ ]
213
+ ]
214
+ ]
215
+ ]
216
+
217
+ SyntaxTree::CallNode.new(
218
+ message:
219
+ SyntaxTree::Ident.new(
220
+ value: "assert_equal",
221
+ location: node.location
222
+ ),
223
+ arguments:
224
+ SyntaxTree::ArgParen.new(
225
+ arguments:
226
+ SyntaxTree::Args.new(
227
+ parts: [expected_expr, actual_expr],
228
+ location: node.location
229
+ ),
230
+ location: node.location
231
+ ),
232
+ location: node.location,
233
+ receiver: nil,
234
+ operator: nil
235
+ )
236
+ end
237
+ ```
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitestify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafe Rosen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-23 00:00:00.000000000 Z
11
+ date: 2022-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: syntax_tree
@@ -24,10 +24,39 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-inflector
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-cli
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
27
55
  description:
28
56
  email:
29
57
  - rafe@existentialmutt.com
30
- executables: []
58
+ executables:
59
+ - minitestify
31
60
  extensions: []
32
61
  extra_rdoc_files: []
33
62
  files:
@@ -38,9 +67,13 @@ files:
38
67
  - LICENSE.txt
39
68
  - README.md
40
69
  - Rakefile
70
+ - exe/minitestify
41
71
  - lib/minitestify.rb
72
+ - lib/minitestify/cli.rb
73
+ - lib/minitestify/spec.rb
42
74
  - lib/minitestify/version.rb
43
75
  - minitestify.gemspec
76
+ - scripts/transform.md
44
77
  - sig/minitestify.rbs
45
78
  homepage: https://github.com/existentialmutt/minitestify
46
79
  licenses: